How to disassemble ECU firmware: from raw readout to readable code

Ghidra · ECU Reverse Engineering

How to disassemble ECU firmware: from raw readout to readable code

Published July 7, 2026 · 13 min read · By Stanislav Kasin

You have a firmware file and a copy of Ghidra. You import the binary and hit disassemble. What comes back is a screen full of jumps that lead nowhere, references into empty space, and code that reads like static. So you assume Ghidra is the wrong tool, or that this ECU is too hard. Neither is true. The disassembly failed before Ghidra ever ran — in the file itself. This guide walks the real sequence: what is inside an ECU readout, the compression trap that breaks most first attempts, how to set up the processor, and how to read the code once it resolves.

What is inside an ECU readout

A complete ECU readout is not “a lot of bytes.” It is several distinct structures living side by side in one file, and the first skill of disassembly is telling them apart.

There are five of them. The bootloader starts the ECU. The program — one or several code blocks — is the software that runs the engine. The vector tables hold the addresses the processor jumps to on reset and interrupts. In a 16-bit hex view they show up as 32-bit values in a distinctive up-down, up-down pattern. There are empty areas — padding between blocks. And there is the calibration: the maps and single values that WinOLS® edits.

Inside a complete ECU readout Inside a complete ECU readout Five separate structures live in one binary file Bootloader starts the ECU Program one or more code blocks Vector tables 32-bit entries, up-down pattern Empty areas gaps the flasher may drop Calibration the maps WinOLS edits
Figure 1. Five structures share one readout file. WinOLS edits the last block; Ghidra reads the program. The empty areas (gold) are exactly what a compressed readout throws away.

Here is the division of labour that matters. WinOLS works with the calibration. Ghidra works with the program. When you tune maps, you live in the last block. When you disassemble, you live in the code that reads those maps and decides what to do with them. That is why disassembly opens doors maps cannot: it is where the logic lives, not just the numbers.

Before you disassemble anything, look at the file once and find those blocks by eye: where the boot sits, where the program ends, where the calibration begins. That habit is what makes the next problem visible instead of invisible.

The compression trap that breaks most attempts

Here is the single most common reason a first disassembly looks like garbage, and almost no guide mentions it.

Most flashers compress the file when they read an ECU. This is not a zip archive — it is simpler and more damaging. The flasher removes the empty space between the program and the calibration, and the file gets smaller. But the code inside still holds the original absolute addresses from build time. Those addresses assume the empty space is still there. Remove it, and the program and the maps end up closer together than they ever were in the real ECU.

Compressed readout vs real ECU memory Compressed readout vs real ECU memory Compressed file (what you got) Real memory layout (what the ECU runs) Empty space removed by the flasher Empty space preserved Maps sit right after the program Calibration at its true address File smaller than the real layout Example: 0x38000 on MED17.1 Jumps point past the data Every reference resolves Disassembly looks like nonsense Code reads like code
Figure 2. Compression keeps the bytes but loses the addresses. Restore the layout on the right and every reference in the code resolves again.

The trap in one line

A compressed readout puts the right bytes at the wrong addresses. The code says “jump to 0x38000,” but after compression the data that belongs at 0x38000 is somewhere else — so the jump lands in nonsense.

The signature is easy to spot once you know it: the gap between the end of the program and the start of the calibration is suspiciously short. The program cannot “reach” the calibration, because in the file they are almost touching. On a Bosch MED17.1, for example, the calibration should begin at address 0x38000. If your file has it starting far earlier, with barely any gap after the code, you are holding a compressed readout.

Disassemble it as-is and Ghidra does exactly what you told it to. It reads the addresses in the code and follows them — straight into data that is not there. Incoherent jumps, references that resolve to garbage, functions that end mid-air. The tool is not wrong. The file is.

The fix is to rebuild the proper memory layout before you disassemble: restore the empty space so every block sits back at its true address. Once the layout is right, the same file that produced static now produces code. This is the step that separates people who read ECUs from people who blame Ghidra.

Learn the rebuild on real firmware

The Ghidra TriCore Fundamental course walks memory-layout rebuild on real Bosch MED17 and EDC17 files — not simplified textbook dumps. It is the only structured Ghidra course built for calibrators rather than programmers.

See the TriCore course →

Setting up the processor: PSPEC and LDEFS

With the layout right, the next question is whether Ghidra actually understands the chip. Out of the box it ships a Universal TriCore language definition. That definition knows the base addressing — Flash at 0x80000000, main RAM at 0xD0000000 — and not much else. It does not know the CAN ports, the Scratchpad RAM, or the Cache RAM of the specific chip in front of you.

For automotive work the specific chip usually is a TC1796 or TC1797 — the two most common controllers inside EDC17 ECUs. To get a richer disassembly, where those regions are labelled instead of showing as bare addresses, you install two things:

  • A PSPEC file (processor specification) that describes the CAN ports, Scratchpad RAM, and Cache RAM of that exact model. PSPEC files go into {Ghidra}/Processors/TriCore/Data/Languages/.
  • An update to the LDEFS file in the same folder — the index that tells Ghidra which language definitions and specifications are available. A new PSPEC that is not listed in LDEFS simply will not appear.

The practical rule from the workbench: it is better to have more memory regions defined and delete the ones you do not need than to hit an address later and go hunting through datasheets to find out what it is. When the program touches a CAN register, you want to read the register’s name, not a raw hex address. That single difference is what makes navigating the code in the later chapters fast instead of painful.

Importing and getting the addresses right

Now the import itself. The reason a correct layout and a correct processor matter before this step is that Ghidra re-bases the file against the addresses you give it. On TriCore, program Flash lives at 0x80000000. If the file is imported with the wrong base, every absolute reference in the code is offset by a constant, and you are back to jumps that miss.

So the order is not cosmetic, it is causal:

  1. Rebuild the memory layout so blocks sit at their true offsets (the compression fix).
  2. Install the PSPEC and update LDEFS so the chip’s regions are known.
  3. Import and re-address against 0x80000000 so the program’s absolute addresses line up.
  4. Disassemble — and now the references resolve.

Why order decides everything

Each step assumes the previous one is done. Disassemble before rebuilding layout and you read shifted bytes. Import at the wrong base and every reference is off by a constant. Get the sequence right and the code reads itself.

There is a second reason to want the base registers right early. TriCore code reaches much of its data through base registers — A0, A1, A8, A9 — rather than full absolute addresses. A dump that is missing part of the file, such as an application-software-only readout, leaves those base-relative references dangling until you tell Ghidra what the base actually is. Recover the base and thousands of references resolve at once. (We walk exactly this case — a Bosch MG1 on an AURIX TC298, ASW-only, addressed through A9 — in a community walkthrough.)

Reading the Listing window in layers

Once disassembly succeeds, you spend your time in the Listing window — Ghidra’s main view. It looks dense until you realise every line is built from the same five zones, read left to right:

  • Address — and this column is synchronised with WinOLS. Copy an address in Ghidra, paste it in WinOLS, and you are at the same place. The two tools work the same file from two angles.
  • Instruction bytes — the raw binary of the instruction and its operands, the same bytes you would see in a hex dump.
  • Instruction — the TriCore mnemonic (MOV, MOVH.A, LEA, LD/ST, CALL/RET, and the jump family).
  • Operands — the instruction’s parameters, colour-coded so registers and constants are distinguishable at a glance.
  • References — where a jump into this point came from. An arrow shows the source; a J marks a jump.

Read a line that way — address, then bytes, then mnemonic, then operands, then references — and any line becomes legible, even before you know every instruction. This is also where Ghidra’s advantage over WinOLS becomes concrete. WinOLS shows you the maps. Ghidra shows you the whole file: the disassembled code, the RAM regions, the metadata, and the maps. It is the more complete view of what the ECU actually is.

Why a tuner does this at all

None of this is reverse engineering for its own sake. The payoff is direct: reading the code lets you do things maps cannot.

The first practical result in the curriculum is disabling a speed limiter without touching a single map — because the limiter is logic, and logic lives in the code. From there the same skill opens the work that has no map at all: building MapSwitch from scratch for runtime calibration switching, RAM coding to change ECU behaviour without a full reflash, and injecting custom features such as launch control or VIN recoding. These are the jobs a customer asks for when the reference map from a similar file does not work — because the algorithm is different, and only the code shows you the algorithm.

That is the real line between working with maps and understanding the ECU. Maps tell you what the ECU is doing. The disassembled code tells you why — and once you can read why, you can change it.

Read any ECU, not just the familiar ones

Ghidra TriCore Fundamental takes you from preparing a firmware file to reading and editing real TriCore code — the foundation for every custom mod in Practice and Mastery. Structured, certified, built for calibrators.

See the learning path →

Frequently asked questions

What tool do I need to disassemble ECU firmware?

Ghidra. It is free, open-source, and supports the processor families found in modern ECUs — TriCore, Renesas, and PowerPC — out of the box. It was originally built by the NSA for cybersecurity work and turned out to be well suited to ECU firmware. A commercial alternative exists (IDA Pro), but for automotive work Ghidra covers everything a calibrator needs at zero cost.

Why does my disassembly show jumps to nowhere?

Almost always because the readout is compressed. Most flashers drop the empty space between the program and the calibration when they read an ECU, so the file is smaller than the real memory layout. The code still contains the original absolute addresses, but the bytes no longer sit at those addresses — so every jump and reference misses its target. Rebuild the correct memory layout before you disassemble and the jumps resolve.

Can I disassemble ECU firmware without a DAMOS?

Yes. A DAMOS speeds map identification, but disassembly does not need one. Ghidra reads the code itself. Once you can follow the program, you locate calibration by tracing the data the code references — no DAMOS or A2L required. DAMOS makes it faster; reading the code makes it possible.

Do I need to know assembly before I start?

No. You read the Listing window in layers — address, instruction bytes, mnemonic, operands, references — and the assembly vocabulary you actually need is small. A handful of TriCore instructions cover most of what you meet, and you learn them in context as you trace real code rather than from a textbook first.

Is disassembling ECU firmware legal?

Analysing firmware to calibrate an engine you or your customer own is standard industry practice — the disassembly itself is a diagnostic and engineering step. What you then do with the result must respect local emissions and road-legality regulations, which vary by country. Understand the rules that apply where the vehicle is used.

About this guide: Written by Stanislav Kasin, co-founder of Tuners Guild, based on the Ghidra TriCore Fundamental curriculum by Thomas Pirowski — 30+ years in ECU reverse engineering and university lecturer at AGH Kraków.

Related: What Is DAMOS? · Bosch EDC17 Tuning Guide · TriCore Reverse Engineering · Learning Path · Jumps to nowhere? The compressed-readout thread · Ghidra: the 5 windows you actually use

Similar Posts