m740recon
June 20, 2026 · View on GitHub
Overview
m740recon is an advanced disassembler and static analyzer for Renesas/Mitsubishi 740 firmware. Beyond producing as740-compatible assembly, it performs recursive-traversal code/data separation, data-flow value-tracking, declarative control-file driven analysis, cross-referencing, and call-graph / JSON reporting to accelerate reverse-engineering of 8-bit Mitsubishi microcontroller ROMs.
m740recon disassembles firmware for many 8-bit Mitsubishi microcontrollers and generates output compatible with the as740 assembler. The 16- and 32-bit Mitsubishi microcontrollers use different instruction sets and are not supported.
The underlying disassembler lineage was developed to disassemble the firmware of the Volkswagen Gamma V and Volkswagen Rhapsody car radios made by TechniSat. Both radios use the M38869FFAHP microcontroller.
Features
-
Identical reassembly. The assembly output of m740recon assembles to a bit-for-bit exact copy of the original binary using as740. A pure-Python structural round-trip verifies the same property without an external assembler.
-
Code / data separation. Starting from the vectors at the top of memory, m740recon uses recursive-traversal disassembly to separate code from data.
-
Symbol generation. Hardware registers, vectors, referenced memory locations, branch labels, and subroutines are named automatically instead of writing hardcoded addresses.
-
Declarative control files (
-c). A da65-style control file supplies the device, entry points, labels/comments, a segment memory map, typed data ranges (byte/word/addr/text), and address-tables that decode dispatch tables into named handlers — instead of editing source. -
Device hierarchy (
-m). Devices are defined as a base/group/part inheritance hierarchy indevices.py, covering the M37450/51, M3802/07/3886, M50734, and many other 740-family parts plus aliases. A device can also gate opcodes its core does not implement so they decode as data; the hook is in place for cores that need it, though no bundled device currently uses it. -
Value-tracking analysis (
-a,--auto-tables). Optional constant-propagation resolves computedjmp [zp]/jsr [zp]targets;--auto-tablesenumerates contiguous ROM jump tables that feed them. Both are additive and reassembly-safe. -
Cross-references (
-x). Annotates each label with the instructions that reference it. -
Reports (
-j,-g). Emits a machine-readable JSON model or a human-readable call graph instead of the listing (see Reports and visualization).
Installation
m740recon is written in Python and requires Python 3.8 or later. You can download the package from this git repository and then install it into a virtual environment with:
$ git clone https://github.com/keithgh1/m740recon.git
$ cd m740recon
$ python3 -m venv ./venv
$ ./venv/bin/pip3 install --editable '.[test]'
After running the above, you can run the disassembler with ./venv/bin/m740recon
or run its unit tests with ./venv/bin/pytest.
Usage
m740recon accepts a plain binary file as input. The file is assumed to be a ROM image that should be aligned to the top of memory. For example, if a 32K file is given, m740recon will assume the image should be located at 0x8000-0xFFFF. After loading the image, the disassembler reads the vectors at the top of memory and starts tracing instructions from there.
$ ./venv/bin/m740recon input.bin > output.asm
The default MCU type is the M3886 series. Other types may be specified with the -m option, e.g. -m M37450 or -m M50734. You can add support for new devices by editing devices.py.
For anything beyond a single top-aligned ROM — a RAM/ROM split, additional entry points, dispatch tables, or data regions — supply a control file with -c instead of editing source. A control file gives the device, a segment memory map, entry points, labels and comments, typed data ranges, and address-tables that decode dispatch tables into named handlers:
$ ./venv/bin/m740recon -c firmware.m740 > output.asm # device + memory map + labels
$ ./venv/bin/m740recon -c firmware.m740 -a -x > output.asm # + analysis + xrefs
The control-file extension .m740 refers to the 740 chip family. A complete, annotated control file that exercises every directive — device, a segment memory map, entry points, labels, comments, typed data ranges, and an address-table — is included at docs/example.m740.
Most binaries include some computed jumps. -a/--analyze tracks register and zero-page values to resolve jmp [zp] / jsr [zp] targets, and --auto-tables enumerates the ROM jump tables that feed them. Addresses that still can't be resolved automatically can be named directly in the control file.
Once disassembled, the output file can be re-assembled to an identical binary using as740. A sample Makefile is included that shows the required as740 commands.
Reports and visualization
In addition to the assembly listing, m740recon can emit two reports derived from the traced program. Both read only already-traced state and print to stdout in place of the listing:
$ ./venv/bin/m740recon -c firmware.m740 --call-graph > callgraph.txt # human-readable
$ ./venv/bin/m740recon -c firmware.m740 --json > report.json # machine-readable
--call-graph lists each routine with the routines it calls and the routines that call it. --json writes a structured model (m740recon-report/1): routines (each with its outgoing/incoming call edges), vectors, symbols, and a full cross-reference map.
The JSON model renders into a Graphviz diagram. A full firmware graph is usually too large to read, so filter to a routine of interest — for example, everything reachable from one routine, two levels deep:
# report_to_dot.py: turn `m740recon --json` output into Graphviz
import json, sys
model = json.load(open(sys.argv[1]))
routines = {r["address"]: r for r in model["routines"]}
by_name = {r["name"]: r["address"] for r in model["routines"]}
root = by_name[sys.argv[2]]
depth = int(sys.argv[3]) if len(sys.argv) > 3 else 2
seen, frontier = set(), {root}
for _ in range(depth + 1):
seen |= frontier
frontier = {e["to"] for a in frontier if a in routines
for e in routines[a]["calls"]} - seen
print("digraph g {\n rankdir=LR;\n node [shape=box, fontname=Helvetica];")
for a in seen:
for e in routines.get(a, {}).get("calls", []):
if e["to"] in seen:
dash = "" if e["type"] == "call" else " [style=dashed]"
print(' "%s" -> "%s"%s;' % (routines[a]["name"], e["name"], dash))
print("}")
$ ./venv/bin/m740recon -c firmware.m740 --json > report.json
$ python report_to_dot.py report.json main_loop 2 | dot -Tsvg -o callgraph.svg
For example, here is a call graph rendered from real firmware — the print path of a Star NX-1020 dot-matrix printer (an M50734-based device), from the line/print dispatcher down to the routine that fires the print head:

The boxes are subroutines that m740recon traced and named, joined by the call and tail-call edges it recovered; the short descriptions are annotations added by hand to make the path readable. The Graphviz source for this diagram is included at docs/nx1020_printpath.dot.
Supported devices
m740recon ships with definitions for 24 740-family parts, selectable by 48 names: the 24 part names plus 24 aliases for sibling part numbers and alternate spellings (some parts have several aliases, many have none). Choose a device with -m, or with the device directive in a control file (-c); an explicit -m takes precedence over the control file. The default is M3886.
$ ./venv/bin/m740recon -m M50734 input.bin > output.asm
Names are case-sensitive and must match exactly. Passing an unrecognized name prints the full list of accepted names:
$ ./venv/bin/m740recon -m bogus input.bin
Unsupported MCU type requested (bogus)! Currently supported: ...
A device definition supplies the interrupt/reset vector table and a symbol table that names the special-function registers (SFRs), I/O ports, and vectors of that part, so the listing reads in datasheet terms instead of bare addresses. Devices are organized as a base/group/part inheritance hierarchy in m740recon/devices.py: a group base (for example the shared core behind M3802/M3807/M3886) defines the common vectors and ports, and each part inherits it and then adds or overrides its own SFRs. A device may also declare opcodes its core does not implement so they decode as data rather than instructions; the hook exists but no bundled device currently needs it.
Note the mix of naming conventions: a few parts carry an M prefix (M3886, M3802, M3807) while most are bare numbers (50734, 50740A, 37410M3). The familiar M3xxxx/M5xxxx spellings (M37450, M37451, M50734) are provided as aliases of the bare-number entries.
Hand-curated cores
Vectors and SFR names for these cores are hand-verified; a datasheet-sourced reference register map for each is included in docs/ (one file per core).
| Device | Aliases |
|---|---|
7450 | M37450, 37450S1, 37450M4, 37450M8, 37450S2, 37450S4 |
7451 | M37451 |
M3802 | — |
M3807 | — |
M3886 | — |
50734 | M50734, 50734_10 |
MELPS 740 devices (datasheet-derived)
These entries were generated from the MELPS 740 databook. Their vectors and register addresses are datasheet-derived and reliable, but the SFR symbol names are heuristic and may be refined — treat the mnemonics as a starting point. None overlaps a hand-curated core above.
| Device | Aliases |
|---|---|
50740A | 50741 |
50742 | 50708 |
50743 | — |
50744 | 50746 |
50745 | — |
50747 | 50747H |
50752 | 50757, 50758 |
50753 | — |
50754 | 50954, 50955 |
50930 | 50931, 50932 |
50940 | 50941 |
50943 | — |
50944 | — |
50950 | 50951 |
50957 | 50959 |
50964 | 50963 |
37410M3 | 37410M4 |
37415M4 | — |
A few parts place their vectors well below the top of a 64K image — 50740A/50752 at 0x1FFE (an 8K space) and 50930/37410M3/37415M4 at 0x3FFE (a 16K space). These are not top-aligned 64K ROMs, so describe their layout with a control-file memory map (-c) rather than passing a plain top-aligned binary (see Usage).
Adding a device
To add a part, add a _define(...) entry to m740recon/devices.py — optionally inheriting an existing base — plus any _alias(...) lines for sibling part numbers that share its register map. No other code changes are required: the new name becomes a valid -m value automatically.
Testing
$ ./venv/bin/pytest
The suite is self-contained: every input is a synthetic fixture or the repository's own testprog.asm, so no external ROM image is required.
The end-to-end reassembly tests use the real as740 + aslink to prove the listing assembles bit-for-bit back to the input. When that toolchain is not found on PATH (or as <tool>.exe in ~/bin) those tests skip and the rest of the suite still passes — the pure-Python structural round-trip (test_roundtrip) keeps re-encoding verified in the meantime. Set M740_REQUIRE_AS740=1 to turn a missing toolchain into a hard failure instead (recommended for CI that must never let the reassembly guarantee go unverified). To run the full check locally, build as740 and aslink from the ASxxxx source and put them on PATH.
Relationship to m740dasm
m740recon is a friendly fork of m740dasm by Mike Naberezny, used under its BSD-3-Clause license. m740dasm provides the proven 740-family instruction set, the recursive-traversal disassembler core, automatic symbol generation, and the bit-for-bit-exact reassembly guarantee.
m740recon builds on that foundation with a static-analysis and reverse-engineering focus, adding:
- declarative da65-style control files (
-c) for device selection, segment memory maps, entry points, labels/comments, typed data ranges, and address-table decoding; - a base/group/part device inheritance hierarchy spanning many 740-family parts and aliases;
- data-flow value-tracking (
-a) to resolve computedjmp [zp]/jsr [zp]targets, with automatic ROM jump-table enumeration (--auto-tables); - cross-reference annotation (
-x); and - human-readable call-graph and machine-readable JSON reports for visualization.
m740recon is an independent project. Mike Naberezny does not endorse, sponsor, or maintain m740recon, and m740dasm is credited only as the basis from which this fork is derived.
Author
m740recon is maintained by Keith Monahan.
The original m740dasm disassembler, on which m740recon is based, was created by Mike Naberezny (m740dasm).
License
m740recon is distributed under the BSD-3-Clause license. The original copyright notice, conditions, and disclaimer — copyright (c) 2018 Mike Naberezny and contributors — are retained in LICENSE. Additions made in this fork are copyright (c) 2026 Keith Monahan. See LICENSE for the full text.