Skip to main content
Glama

FwAnalyzer (AxFInspector)

Read a firmware .axf like a debugger would: crash addresses, machine code, memory usage, struct layout, stack depth — from one ELF file, with no IDE and no build step.

English | 简体中文

axf_inspector.py is a dependency-light Python script (one required package) that parses an ELF/DWARF firmware image produced by any ARM toolchain (ARMCC, ARMClang, GCC). It turns the build artifact into structured, machine-readable answers that a human or an AI agent can act on. Every command is read-only.

What it answers

Question

Example

A HardFault left PC=0x08001234 — which line is it?

--addr2line 0x08001234 (prints the inline chain too)

What did the compiler emit for this function?

--disasm app_task --mixed

Which functions/data objects eat the most Flash/RAM?

--top --type func / --top --type data --region ram

Which static objects are never referenced?

--unused --min-size 64

What is the real memory layout of this struct?

--struct demo_ctx --expand

How is the image laid out (segments/headers)?

--segments

Which ABI / FPU / compiler options was this built with?

--build-info

Where does this symbol live?

--symbol-addr demo_buffer

Are two builds identical?

--checksum

How big is this function's stack frame? How deep can this call chain get?

--stackframe app_task / --stackdepth main

Each answer can be emitted as plain text (for humans) or --json (for agents/tools).

Related MCP server: toothpick

What it is not

This is a binary-level analyzer. It deliberately does not do source-code analysis:

  • who calls a function in the source (inlining, dead code, function-pointer call sites) → use grep / your editor

  • macro expansion, include dependency graphs, variable data flow, cyclomatic complexity → source tooling

--callgraph uses the ELF relocation table and therefore only sees static direct calls after linking. It is not a source call graph.

Requirements

pip install -r requirements.txt     # pyelftools (required), capstone (optional)

Requirement

Notes

Python 3.10+

the script uses PEP 604 type unions

pyelftools>=0.29

ELF/DWARF parsing

capstone>=4.0

optional; enables real disassembly, otherwise --disasm prints a hex dump

Firmware .axf built with -g

DWARF is required for --addr2line, --struct, --disasm --mixed, and the CFI path of stack analysis

Keil/MDK does not need to be installed. Any ELF32/ELF64 .axf works.

On Windows consoles set PYTHONUTF8=1 when paths contain non-ASCII characters.

Prefer a dedicated debug build when you analyze firmware: high optimization changes what ends up in the .axf, and several answers become less faithful.

Setting

Why it matters for this tool

-O0 or -Og

keeps functions and locals visible; inlining and code motion at -O2/-O3 hide calls and variables from --addr2line, --callgraph and --stackframe/--stackdepth

-g (DWARF)

required by --addr2line, --struct, --disasm --mixed and the CFI path of stack analysis

-fno-inline

optional; preserves call edges for --callgraph and the stack-depth model

-fno-omit-frame-pointer

optional; more reliable unwind and stack-frame evidence

no LTO for the analysis build

LTO merges and drops code, weakening call-graph and stack-depth results

  • Keep the normal optimized image for release, size ranking, checksum and real stack behaviour; use the analysis build for source correlation, call graph and stack estimates.

  • Do not ship the analysis image, and do not compare checksums across different optimization levels.

  • The tool still works on optimized images, but conclusions are weaker — always report the image hash and its build options (--build-info) alongside a finding.

  • Flag spelling depends on the compiler (ARMCLANG/AC6 and GCC accept the forms above; older ARMCC uses its own equivalents) — set them in Keil or your build system.

Quick start

python axf_inspector.py --axf firmware.axf --addr2line 0x08001234
python axf_inspector.py --axf firmware.axf --disasm main --mixed
python axf_inspector.py --axf firmware.axf --top --type data --region ram
python axf_inspector.py --axf firmware.axf --unused --min-size 64
python axf_inspector.py --axf firmware.axf --struct demo_ctx --expand
python axf_inspector.py --axf firmware.axf --stackframe app_task
python axf_inspector.py --axf firmware.axf --stackdepth main --json

Example (--addr2line):

0x08001234: main.c:100
  └─ inlined from: ring_buffer.h:25 (ring_buffer_push)
      └─ inlined from: uart_irq.c:80 (UART_IRQ_Handler)

Command reference

python axf_inspector.py --axf <firmware.axf> <mode> [options]

Mode

Purpose

--addr2line HEX

address → source file:line (inline chain expanded)

--callstack HEX

alias of --addr2line, named for crash investigations

--disasm FUNC [--mixed]

disassemble a function; --mixed interleaves source

--stackframe FUNC

one function's stack frame (CFI first, prologue scan fallback)

--stackdepth ENTRY

accumulated stack depth along the deepest call chain from ENTRY

--top [--type func|data] [--limit N] [--region all|ram|flash]

symbol size ranking

--unused [--min-size N] [--include-funcs]

candidates for unreferenced static objects

--struct NAME [--expand]

real struct/union layout from DWARF

--callgraph FUNC [--depth N]

static call tree (direct calls only)

--segments

program headers / segment map

--build-info

toolchain, architecture, ABI, FPU, alignment

--symbol-addr NAME

absolute address and section of a symbol

--checksum [--section LIST]

SHA256 of selected sections (default .text,.rodata,.data)

--audit-tasks MANIFEST --project-root DIR

optional stack-budget audit against your own allocation manifest

--json

machine-readable output for every mode

--indirect-penalty N

per-indirect-edge uncertainty for --stackdepth (default 80 B)

--safety-factor F

model multiplier for --stackdepth (default 1.5)

--context-bytes N

extra context budget in bytes (legacy alias --freertos-context, default 0 = not included)

--version, -h/--help

version / usage

Stack-depth estimation (not RTOS-bound)

Stack analysis is a pure instruction-level model and makes no assumption about your execution model:

  • --stackframe FUNC reads CFI rules with an SP-relative CFA when available, otherwise falls back to decoding the Thumb prologue (PUSH / SUB SP / ADD SP). It reports the modelled frame, its source, and the instruction evidence.

  • --stackdepth ENTRY accumulates frames along the deepest path from any entry symbol: main, an ISR, an RTOS task function, a business function, or a test stub. The tool does not discover entries, infer allocation units, or apply any minimum-stack/alignment policy.

  • --context-bytes adds an explicit budget for exception frames or registers saved by a runtime; default 0 means not included, and it is never inferred.

  • Open-coded results are model values, not runtime peaks: alloca, register spilling and stack protectors change real usage. Recursion/re-entry means the true worst case is unknown and must be reported as uncertain.

If you already have an allocation budget (bare metal or RTOS), --audit-tasks compares it against the model using a project-owned manifest:

python axf_inspector.py --axf firmware.axf --audit-tasks my-budget.json --project-root . --json

Template: task-audit-template.json (schema_version=2). The arithmetic and status codes (MISSING_SYMBOL, MODEL_OVER_BUDGET, INCOMPLETE, NEEDS_RUNTIME_CHECK) are documented in SKILL.md.

Using it from an AI agent

Two ways, same engine — the MCP server calls axf_inspector.py directly, so CLI and MCP cannot drift.

MCP (preferred)

mcp_server.py is a zero-dependency stdio server exposing 14 read-only tools: axf_file_info, axf_build_info, axf_symbols_top, axf_unused, axf_segments, axf_symbol_addr, axf_checksum, axf_addr2line, axf_disasm, axf_struct, axf_callgraph, axf_stackframe, axf_stackdepth, axf_audit_tasks. The parsed image is cached by (path, size, mtime), so a multi-step investigation parses DWARF once instead of once per command.

Host

Setup

Claude Code

project .mcp.json is included; or claude mcp add axf-analyzer -- python /abs/path/mcp_server.py

Codex

~/.codex/config.toml → [mcp_servers.axf-analyzer] with command = "python", args = ["/abs/path/mcp_server.py"]

OpenCode

opencode.json → {"mcp":{"axf-analyzer":{"type":"local","command":["python","/abs/path/mcp_server.py"],"enabled":true}}}

any MCP client

stdio server: run python mcp_server.py (JSON-RPC on stdin/stdout)

No extra packages: the server uses only the standard library plus this analyzer.

Skill + CLI (fallback)

SKILL.md is a portable skill definition (name + when-to-use description + commands + safety rules). Put this directory (or just SKILL.md) wherever your agent looks for skills — Claude Code ~/.claude/skills/fw-analyzer/SKILL.md, a DSH skills directory, or any framework that accepts instruction files — then let the agent run the CLI and read --json. This path needs only shell access; it is the right choice when the host has no MCP support.

Repository layout

axf_inspector.py            the analyzer (single file, stdlib + pyelftools)
mcp_server.py               zero-dependency MCP stdio server (14 tools, image cache)
.mcp.json                   Claude Code project-scope MCP registration
SKILL.md                    portable agent skill definition
README.md / README.zh-CN.md this document (English) / Chinese translation
requirements.txt            pyelftools (required), capstone (optional)
task-audit-template.json    template for the optional --audit-tasks manifest
test_stack_audit.py         unit tests for stack-budget maths and evidence handling
test_mcp_server.py          MCP transport/tool tests (builds a fixture when armclang is available)

Limits and troubleshooting

Symptom

Cause / fix

--addr2line / --struct reports no debug info

the image was built without -g; rebuild with debug info

--disasm prints a hex dump

capstone is not installed (pip install capstone>=4.0)

Old ARMCC images fail to parse DWARF

DWARF 2/3 images are partially supported; confirm -g and try --build-info

Very large .axf is slow

DWARF-rich images are heavy; prefer --top, --segments, --symbol-addr

--callgraph misses calls

direct/static calls only — function pointers, tail calls, inlined calls and dead code are invisible

Inlined code hides lines or call edges

the image is optimized; analyze a dedicated -O0/-Og -g build (see Build settings for analysis)

--unused hits a live symbol

debug info, linker-script symbols and indirect references can look unused; this is a candidate list

Non-ASCII paths garble on Windows

$env:PYTHONUTF8 = "1"

Development

python -m unittest discover -s . -p "test_*.py" -v
python axf_inspector.py --version

Tests cover stack-budget arithmetic, model boundaries, evidence handling and the MCP transport. ELF-based tests build a fixture with Keil's armclang and are skipped when it is absent.

Design notes

  • One input, no project files: the tool never reads .uvprojx, never runs a build and never needs Keil.

  • Evidence over guesswork: every number carries its source (CFI, prologue scan, section flags, relocation table, DWARF DIE), and uncertain models are labelled as such.

  • Read-only by construction: the .axf, sources and project files are never modified; there is no network access and no output besides stdout.

  • Decoupled by design: nothing assumes a chip model, compiler, section layout or RTOS. The optional audit manifest accepts any execution model.

License

MIT — see LICENSE. Copyright (c) 2026 wimi996123.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to locally analyze and reverse engineer binaries through a compact 12-tool MCP interface for triage, function discovery, disassembly/decompilation, cross-references, strings/imports, call graphs, memory reads, annotations, and binary diffs.
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI coding agents to inspect and reverse-engineer large binaries through bounded stdio JSON-RPC 2.0 tools for graph queries, SSA facts, path solving, runtime grounding, and mod scaffolding.
    1
    AGPL 3.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents (and a matching CLI) to inspect native binaries, JavaScript/Electron apps, .NET assemblies, Go/Rust/WASM artifacts, EVM bytecode, SQLite databases, recorded crashes, and websites without source code, explaining how features work and returning the evidence and limitations behind each conclusion. Analysis runs locally, using optional Hopper, Ghidra, or IDA providers for native targets.
    500,256 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Exposes 18 typed tools that drive real static-analysis engines (Ghidra, apktool, WABT, TShark, evmole, Rizin) over a single MCP endpoint to decompile binaries, audit APKs, scan EVM contracts, inspect WebAssembly, firmware, SQLite, and pcap data. Every call returns signed, verifiable evidence with an evidence_id, provider identity, SHA-256 artifact digest, and explicit limitations for security research, malware analysis, forensics, and CTF work.
    MIT