ArchCheck
Architecture recovery for C/C++ firmware: ArchCheck reads the source and recovers its runtime structure — entry points, tasks, ISRs, callbacks, loops, state machines, shared state and concurrency boundaries — without running the program, attaching a probe, or calling an LLM.
Every finding carries a file, a line and a confidence level. Anything that cannot be derived from the source and a rule is reported as unknown, never guessed.
pip install archcheck
archcheck path/to/project --out report/ # or let an agent run it: see the repository README
Repository · Field contract · Usage · Status & known limits · Live demo
Why
To see how a firmware actually runs, the standard answer is a runtime tracer: instrument the build, flash it, capture a trace. That tells you what the system did. ArchCheck answers a different question, and it answers it before you have a working build to trace:
What does the code say it will do?
| Requires | Answers | |
|---|---|---|
| Percepio Tracealyzer | TraceRecorder instrumentation, a running system | what the system did |
| Segger SystemView | J-Link probe + instrumentation | what the system did |
| ArchCheck | a compilation database | what the code says it will do |
Useful when you inherit firmware nobody can explain any more, when you want the interrupt/task sharing map before a refactor, or when you want an architecture check in CI that fails on a new cross-layer call.
If you can build it, ArchCheck can read it
compile_commands.json(CMake, ESP-IDF, Zephyr, Makefile + bear), or a Keil.uvprojx— ArchCheck generates a compatible database from the Keil project itselfclangdonPATH(the AST layer is built on its LSP)- Python 3.10+
That is the entire prerequisite list. There is no agent to install, no code to annotate, and nothing is uploaded anywhere.
Quick start
git clone https://github.com/SHAR-K/archcheck
cd archcheck && pip install -e .
# a CMake / ESP-IDF / Zephyr project
archcheck /path/to/firmware
# a Keil project (pass --keil-target when the project has several targets)
archcheck /path/to/firmware --keil-project MDK-ARM/app.uvprojx
Point it at your own firmware. Three minutes later you have your startup chain, your interrupt vectors, and the variables your ISRs share with your tasks — with line numbers. That is a better demo than any screenshot here.
Output lands in <project>/.arch-report: architecture.json (the facts),
metrics.json, a Markdown summary, and a single self-contained report.html.
What comes out
- Entry points and startup chain —
main, reset handler,*_initcall chain - Runtime units — tasks, ISRs, callbacks (including callbacks registered through initializer tables, struct fields and function-pointer parameters), each with its registration site
- Run modes — periodic / busy-poll / event-driven / one-shot, with the blocking call and the period that implies it
- Loops — bounds where they can be derived, and unbounded busy-waits flagged
- State machine candidates —
switchdispatch variables, states and transitions - Shared state and conflict candidates — which variables are reached by more than one runtime unit, which accesses sit outside a recognised critical section
- Image facts — code/RO/RW/ZI per module from an
armlinkmap, kept as a separate evidence source from the source-derived facts - Coverage — which translation units were analysed and which were not, per directory
Reference projects
Two public repositories. Every number below is reproducible on your machine.
| hoverboard-sideboard-hack-GD | gd32f30x | |
|---|---|---|
| Model | bare-metal super-loop | FreeRTOS |
| Translation units | 36 | 51 |
| Functions | 689 | 871 |
| ISRs | 13 | 10 |
| Loops | 55 | 242 |
| State machine candidates | 13 | 12 |
| Shared resources | 16 | 25 |
| Conflict candidates | 0 (see below) | 9 |
| Wall clock | 9.6 s | 17.0 s |
docs/ARCHCHECK_STATUS.md §4 has the full tables and the exact commands.
What it does not do
It does not call an LLM. Not for facts, not for naming, not for guessing. The rule set is data (see below); an agent can propose new rules for you to review, but facts only ever come from source plus a rule.
Two known defects, both being worked on:
- Tasks created through a wrapper are missed. If your project wraps the RTOS API
(
OS_TaskCreate→xTaskCreate(task, ...)where the entry arrives as a parameter), the task entry is not recovered — on thegd32f30xreference project the only task reported is FreeRTOS's own idle task. Fix is parameter back-tracking through one wrapper hop. - A bare-metal
mainis not counted as the other side of an ISR conflict. On the hoverboard project all 14ISR + mainshared variables are found correctly, with line numbers and access directions — but the conflict rule only pairs an ISR against atask/callback/timer, so it reports zero conflicts. This affects every super-loop project.
Beyond that: cross-function critical sections need a CFG and are only approximated;
yieldLocals compares source line order, not control flow; sizes require a linker map
and only armlink maps are parsed today. Every approximation is also declared in
astFacts.approximations in the output itself.
Rules are data
The engine knows no API name of its own. FreeRTOS, Zephyr, CMSIS-RTOS2, POSIX,
protothreads, ESP-IDF, STM32 HAL, GD32 and Cortex-M CMSIS ship as YAML profiles under
src/archcheck/profiles/. Built-in profiles only carry API names you can look up in
public framework docs or a vendor SDK.
Your own wrappers go next to your project, no fork required:
# <project>/framework_rules.yaml
scheduling: cooperative
task_create:
- function: os_task_add
entry_argument: 0
Adding support for a kernel ArchCheck has never seen is a YAML file, not a patch. See CONTRIBUTING.md — profile contributions are the most useful kind and need no Python.
License
MIT © 2026 SHAR-K
Metadata
Release files for archcheck 0.26.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| archcheck-0.26.0.tar.gz | 201.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| archcheck-0.26.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 410.6 kB
Release files / archcheck-0.26.0.tar.gz
| Download URL | archcheck-0.26.0.tar.gz |
|---|---|
| Size | 201.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e6864a52e1bf8f549efb86ff7c455943fab7da06f3f59e9b6dbfb27443018830
|
|
BLAKE2b-256 checksum How to use checksums |
c33bedce9bd91eec2de5bf7661e3bfd93703087e6d00c41f76e566af2083e84c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency logRelease files / archcheck-0.26.0-py3-none-any.whl
| Download URL | archcheck-0.26.0-py3-none-any.whl |
|---|---|
| Size | 209.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a72f7ef6ecf604c1e846647f52682dba22fd170894a5f1e6cc6f2cc6c4201c71
|
|
BLAKE2b-256 checksum How to use checksums |
708324b9df9297ac3698a0034861ac525ae5b728390087651635c3d89e37e4aa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency log