Open-source project
matt-kempster/m2c avatar
matt-kempster/m2c

m2c and the decompiler that aims for byte identical recompilation

A decompiler targeting MIPS, PowerPC, ARM, and SuperH.

666 stars77 forksPythonGPL-3.0

At a glance

What is it?
m2c turns 32-bit MIPS, ARM, PowerPC and SuperH assembly back into C, and its design goal is not readability but matching: the C it emits is meant to compile back to the same bytes. It fits a decompilation project working against a specific compiler, and it is the wrong tool for understanding an unfamiliar binary.
Who is it for?
m2c fits a decompilation project with a fixed target, where the goal is a C source that recompiles to the same bytes and where you are working alongside asm-differ or decomp-permuter rather than alone. It is the wrong tool for reverse engineering an unfamiliar binary, since its output is written for a compiler to consume rather than for a person to read, and its coverage stops at 32-bit architectures.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The goal is a recompile, not a readable listing

m2c stands for machine code to C, and it is described as a decompiler for 32-bit MIPS, ARM, PowerPC and SuperH assembly that produces C code, with partial support for C++.

What makes it different is stated in the readme directly. The project, initially named `mips_to_c`, has the goal to support decompilation projects, which aim to write source code that yields byte-identical output when compiled with a particular build system. Its focus on finding matching C source differentiates it from other decompilation suites, such as IDA or Ghidra.

That is a sharply different objective from the usual one. A general purpose decompiler wants to produce C that a human can read and modify, and it will accept that the result is not semantically identical to the original. m2c wants to produce C that a specific compiler turns back into the same bytes, which is a much stricter test and a much more useful one if you are trying to match a build rather than understand a program.

Two consequences follow. The first is that output quality is judged by whether it compiles and matches, not by whether it looks idiomatic. The second is that the tool is target-specific in a way a general decompiler is not, because the heuristics depend on what a particular compiler does with a particular ABI.

The readme is also honest about where it stands today: right now the decompiler is fairly functional, though it sometimes generates suboptimal code, especially for loops. That last caveat matters more for a matching workflow than it would for a reading workflow, since a suboptimal loop body usually means the bytes will not match.

The target flag is the compiler, the ABI and the language in one string

The configuration model is unusual and worth understanding before anything else, because it drives most of the tool's behaviour.

The output's platform, compiler and source language are collectively referred to as a target, and it is passed with the `-t` or `--target` flag, for example `--target mips-ido-c`.

The supported platforms are `mips` for MIPS with the O32 ABI, `mipsel` for the same in little endian, `mipsee` for MIPS with eabi64 in little endian, `ppc` for big endian PowerPC, `arm` for little endian ARM, `gba` for ARM with APCS in little endian, and `sh2` for big endian SuperH.

The supported compilers are `ido`, the Integrated Development Option from SGI, `gcc`, the GNU C Compiler, and `mwcc`, the MetroWerks CodeWarrior toolchain. The supported languages are `c` and `c++`.

Each of those compilers has its own habits, and the readme gives one example of the kind of detail that is encoded: the decompiler can demangle C++ symbol names as used by CodeWarrior. The `mwcc` target is CodeWarrior, so this is not a generic feature.

The fact that the toolchain list includes a compiler from the late 1990s tells you what the project is for. It originally targeted popular compilers of that era, and it also works well with newer compilers or hand-written assembly. Those two cases have very different difficulty: matching an old compiler's output is a known problem with a known target, while hand-written assembly is a reverse engineering problem with no target to match.

The target also changes what the tool believes about calling conventions, register allocation and struct layout, which is why there is no sensible default and why the flag is effectively mandatory.

Context files are how you tell it about types it cannot infer

The inference machinery is the heart of the tool, and the context file is how you correct it.

The context is a C file you write describing types, and m2c reads it to fill in what it cannot work out. Context files provided with `--context` are parsed and cached, so subsequent runs with the same file are faster, with the cache for `foo/bar.c` stored in `foo/bar.m2c`. These cache files can be ignored, added to `.gitignore`, and are automatically regenerated if the context files change. Caching can be disabled with the `--no-cache` argument.

Three inference areas are documented.

For stack variables, the stack is represented internally as a struct, so it is possible to manually specify the names and types of stack variables by providing a struct declaration in the context. m2c looks for a struct with the tag name `_m2c_stack_<function name>`, for example `struct _m2c_stack_test` for a function `test()`. The size of the stack must exactly match the detected frame size, or m2c will return an error.

For struct fields, m2c can use type information from decompiled functions to fill in unknown struct fields, and this can be disabled with `--no-unk-inference`. The fields treated as unknown space that can be inferred are `char` arrays with a name starting with `unk_`, such as `char unk_10[4];`, and any field with a type that starts with `UNK_` or `M2C_UNK`. Struct field inference only works on structs without bitfields or unnamed union fields, and the output includes declarations for any struct with at least one inferred field.

For global declarations and initializers, when the input files have `data`, `rodata` and/or `bss` sections, m2c can generate the initializers for variables it knows the types of, and qualifier hints such as `const`, `static` and `extern` are based on which sections the symbols appear in. It cannot generate initializers for structs with bitfields or for symbols whose type it cannot infer.

The globals behaviour has three modes. `--globals=used` is the default, emitting global declarations for referenced symbols with initializers generated when the data and rodata sections are provided. `--globals=none` disables globals entirely, so only function definitions are emitted. `--globals=all` includes everything in `used` plus initializers for unreferenced symbols, and can be used to convert data and rodata files without decompiling any functions.

This is where the decompiler earns its keep, and where the manual labour goes: the context is the part of the work a person does, and the inference is the part it does for you.

Assembly in, and the same command drives the website

The command line is one line, and the options are worth reading as a description of the workflow.

bash
python3 m2c.py [options] [-t <target>] [--context <context file>] [-f <function name>] <asmfile>...

The input is expected to match the GNU `as` assembly format, produced by tools like spimdisasm, and the `tests/` directory holds some example input and output. The assembly file is the input rather than the binary, which is the second major structural difference from a general purpose decompiler. There is no disassembly step inside m2c, because the disassembly and the decompilation have to agree about what a function is, and feeding a fixed disassembly is how the project keeps them in step.

By default m2c decompiles all functions in the text sections from the input assembly files, and it is able to perform a small amount of cross-function type inference, if the functions call each other. You can limit the functions decompiled by providing `-f <function name>` flags.

The website is the same tool with a different front end. The readme links an online version at simonsoftware.se, and several options are described with their website equivalents: the Function dropdown corresponds to `-f`, Global declarations to `--globals`, Disable unknown struct/type inference to `--no-unk-inference`, and Stack struct templates to `--stack-structs`.

The stack struct workflow is a good example of the intended loop, and it is described end to end for the `custom_stack` test. You run m2c with the `--stack-structs` option to get the inferred struct for the function, for example `python3 m2c.py tests/end_to_end/custom_stack/irix-o2.s -f test --stack-structs`, and the output includes the inferred stack declaration, which can then be edited and provided as context by re-running m2c.

So the workflow is generate, edit, feed back. The human supplies the types the machine could not infer, and the machine supplies the control flow.

The tool is one part of a decompilation toolchain, not a replacement for one

m2c is often used in decompilation workflows with splat, asm-differ and decomp-permuter, and that sentence is the clearest description of its position in the ecosystem.

The division of labour is the point. A disassembler produces the assembly that a human reads and that a decompiler consumes. asm-differ compares two builds, so you can tell whether a change to your C actually changed the output. decomp-permuter searches over candidate implementations to find one that matches the original bytes. And m2c produces the C from the assembly.

The matching loop is what makes the whole thing coherent. You write C, you compile it, asm-differ tells you whether the bytes match, decomp-permuter proposes alternatives when they do not, and m2c gives you a starting C to permute. A decompiler that is not aiming for byte identical output does not fit into that loop, because the goal there is a close approximation rather than an exact one.

The same reasoning explains the target flag. A general decompiler wants to be right about a wide range of code. m2c wants to be right about one compiler on one platform, and the `ido` and `mwcc` targets exist because the old compilers that decompilation projects target all had their own idiosyncrasies.

The online version and the browser interface in the repository listing, along with `website.py`, mean the same decompiler can be driven from a browser as well as from a terminal. For a project that is a collaborative, iterative activity, that matters, because the expensive part is the human editing of the context and the generated C, and a browser interface is easier to put in front of a group than a command line with a context file argument.

The package also pulls in graphviz, which is the one runtime dependency listed in the project metadata, and it is there for drawing the control flow of the recovered functions rather than for anything to do with the decompilation itself.

Where m2c is the wrong tool

There are three cases where m2c is the wrong choice, and the first is the most common one.

The first is reverse engineering an unfamiliar binary. m2c is not a disassembly and analysis tool. Its input is assembly in the GNU `as` format, and its output is C written for a compiler, with the decompiler's own conventions for types, stack layout and control flow. It sometimes generates suboptimal code, especially for loops, and it has partial C++ support. If the goal is to understand what a program does, this is the wrong instrument, and a general purpose decompiler is the right one.

The second is anything outside 32-bit MIPS, ARM, PowerPC and SuperH. The platform list is closed: seven entries, and the ABI variants are spelled out. A 64-bit target, x86, RISC-V or anything else is simply out of scope, and there is no target string for them.

The third is a one-off task. Getting a context file right, feeding the stack struct back in, and iterating on the generated C is a workflow with a setup cost. For a single function where you just want to know what it does, that setup is not worth it.

There is a subtler limit worth naming for teams that will use this in anger. The tool infers types, and its inference is bounded: struct field inference does not work on structs with bitfields or unnamed union fields, initializers cannot be generated for structs with bitfields, and globals need a type you supply if the tool cannot infer it. Every one of those is a case where the human has to intervene, and the size of that intervention grows with the complexity of the original code. Bitfield-heavy code, which is common in older games, is close to the worst case.

The practical rule is that m2c is a component for a matching workflow, and the component it fills is generating compilable C from assembly. If your project is not trying to make a build match, or your target is not on the list, look elsewhere.

Editorial conclusion

m2c fits a decompilation project with a fixed target, where the goal is a C source that recompiles to the same bytes and where you are working alongside asm-differ or decomp-permuter rather than alone. It is the wrong tool for reverse engineering an unfamiliar binary, since its output is written for a compiler to consume rather than for a person to read, and its coverage stops at 32-bit architectures. Before you commit to it, verify three things: that your platform and compiler pair is one of the listed targets, since the heuristics are compiler specific, that you can supply the GNU `as` assembly format the tool expects, and how much of the output you are willing to hand edit, because the readme says the decompiler sometimes generates suboptimal code, especially for loops. The code is GPL-3.0-only, the repository publishes no releases, and the last push was on 2026-09-16.

Frequently asked questions

What is m2c and how does it differ from other decompilers?

m2c is a decompiler for 32-bit MIPS, ARM, PowerPC and SuperH assembly that produces C with partial C++ support. Its focus is on finding matching C source, the kind of source that yields byte-identical output when compiled with a particular build system, rather than on producing readable output like IDA or Ghidra.

What input does m2c expect?

The input is expected to match the GNU `as` assembly format, produced by tools like spimdisasm, and the `tests/` directory holds example input and output. It does not take a binary, so disassembly is a separate step the user performs.

Which platforms and compilers does m2c support?

The supported platforms are `mips`, `mipsel`, `mipsee`, `ppc`, `arm`, `gba` and `sh2`, and the supported compilers are `ido` from SGI, `gcc` and `mwcc` for MetroWerks CodeWarrior. The combination is passed with the `-t` or `--target` flag, for example `--target mips-ido-c`.

What is a context file in m2c and what does it do?

It is a C file describing types that m2c reads to fill in what it cannot infer. It is used for stack variables, where m2c looks for a struct named `_m2c_stack_<function name>` and requires the size to match the detected frame size exactly, and for unknown struct fields, where names starting with `unk_` or types starting with `UNK_` or `M2C_UNK` are treated as inferable space.

How is m2c used with other decompilation tools?

The readme says m2c is often used in decompilation workflows with splat, asm-differ and decomp-permuter. A disassembler produces the assembly, asm-differ compares builds, decomp-permuter searches for a matching implementation, and m2c generates the C that is permuted and compared.

Official sources

  1. Issues
  2. License: GPL-3.0
  3. matt-kempster/m2c on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/matt-kempster-m2c.svg)](https://hysenlabs.com/projects/matt-kempster-m2c)

Was this useful?