## Thursday, May 9, 2013

### Reverse Engineering GM ECMs

This is the tale of how I wrote a Perl disassembler for the Motorola 6801 CPU to learn how the fuel injection computer that I installed onto my Jeep works.

Almost 200 pages of source code and data create code to run late-80's GM fuel injected engines. DIYers retrieved machine code, disassembled it, and reverse engineered code and hundreds of parameters 15 years ago.

Complete ASDZ Source for 1227747 (pdf)

 Inside a 1227747 GM ECM
Fuel injection computers interface with fuel pump relays, fuel injectors, ignition modules, coolant, throttle, oxygen, and pressure sensors. They employ complex algorithms to properly meter fuel under myriad engine and environmental conditions.

Only recently has my 1986 Jeep Grand Wagoneer benefited from this technology when I retrofit an 80's GM 1227747 ECM, throttle body, and all the fixin's. While I use nice GUI software to tune the parameters, I wanted to peer into the code that is executed on a 1MHz Motorola 6800-family CPUs with a paucity of memory.

On a whim I decided to write a disassembler in Perl with all the features that were lacking from other offerings. Plus, writing one is a great way to learn about a CPU.

Source code repository

CPUs in the 6800 family are simple. They have two accumulator registers (A, B) and one index register (X) as well as a stack pointer (SP), and program counter (PC). A status, or condition code register (CCR) stores the status results of computation: carry, zero, overflow, and so on. The MOS 6502 (Apple //e) and related MOS 6510 (Commodore 64) are even simpler, designed as a low cost alternative by some of the same engineers.

## MC6800 Assembly

Here's some assembly language from the GM ECM.

LDX #$D4D9 ; O2 SENS VOLTAGE BIAS FOR COLD OP'S TBL LDAA$00E3 ; START UP COOL
COMA
JSR $FB36 ; 2d LK UP, WITH UPPER LIMIT STAA$0055 ; SAVE o2 SENSOR VOLTAGE BIAS RESULT

In the example above,
• The mnemonic LDX loads the operand, the value 0xD4D9, indicated by the # prefix, into the index register X.
• Then, mnemonic LDAA loads the A accumulator with a value stored in memory location 0x00E3.
• COMA performs a binary complement on register A.
• JSR jumps to subroutine at location 0xFB36 in memory. After it returns, the result returned in the A register is stored to memory location 0x0055.

### Mnemonics

Assembly mnemonics represent simple instructions for the CPU to carry out. Operands like #$D4D9,$00E3, $FB36, and$0055 are addresses or values used by the instruction. They specify the addressing mode of the instruction.

The 6800 family instructions can have Immediate Mode (value) operands like #$D4D9, Direct Mode operands referring to the first 256 bytes in memory like$0055, Extended Mode operands that can reference any memory such as $FB36, and Index Mode operands which use the X register as a memory pointer, indicated by ",X" in assembly language. There's also relative addressing used for branch/jump operations. The operand specifies a relative address, positive or negative, to jump to. Inherent mode has no operand; the registers involved are implied, such as ABX, add B to X. ## Writing a Disassembler ### Assembling When assembly language is assembled into machine code, the mnemonic and addressing mode determine the opcode to use. A given mnemonic has an associated opcode for each addressing mode. For example, ADDA is a mnemonic which can have immediate, direct, index, and extended addressing modes: • ADDA #$04 becomes opcode 8B, operand 04
• ADDA $0004 becomes opcode 9B, operand 04 • ADDA$04,X becomes opcode AB, operand 04

my $calcAddr =$addr + 2 + $a1; ### Labels I wanted to automatically generate labels, which are assembly langauge conveniences that identify addresses and can be used by jump and branch instructions making code much easier to read and maintain. Here's an example from rosettacode.org: outeee =$e1d1      ROM: console putchar routine
.or  $0f00 ;-----------------------------------------------------; main ldx #string Point to the string bra puts and print it outs jsr outeee Emit a as ascii inx Advance the string pointer puts ldaa ,x Load a string character bne outs Print it if non-null bra main else restart ;=====================================================; string .as "SPAM",#13,#10,#0 .en Without labels, bra puts would be replaced by a relative value. Same with bne outs and bra main. Likewise, jsr outeee would be replaced with jsr$e1d1 which is harder to understand.

To generate labels, I disassembled the code in two passes. The first pass identifies address references. The second pass converts opcodes and operands into assembly, while replacing raw address references ($e1d1) with labels (LE1D1), and prefixing referenced addresses with labels. Like this: bmi _Ld62c ldab D05 andb #$80
aba
_Ld62c: ldab  D10

As you probably noticed, I also converted direct mode references to labels of the form DNN.

### Variables

The first several hundred bytes of GM ECM binary file is filled with parameters. Rather than using generic labels, my disassembler reads a list of addresses and label (variable) names. Here's an excerpt of the config file:

d290,TPS_FiltCoef1,FILT COEF TPS
d291,TPS_FiltCoef2,FILT COEF TPS
d292,DiffTPSforPE,DIFF TPS REQ FOR PWR ENRICH WHILE IN PE

d293,IAC_BPW,usec ADDED TO BPW WHILE IAC IS OPENING

So, for example, any reference to \$0D293 is converted to IAC_BPW, the Idle Air Control Base Pulse Width. I did this for all the addresses referenced in the bin itself. It makes the code more readable.

ldaa  IAC_BPW ; ADDED TO BPW WHILE IAC IS OPENING
_Ld79f: adda  D52     ; BPW,LSB
bcs   _Ld7a7  ; BR IF NO OVERFLOW
adda  D53     ; BPW,MSB
bcc   _Ld7a9  ; BR IF NO OVERFLOW

There are commented disassembly files out there. Adding those comments (as above) to my disassembly using descriptive variable names should reduce the time it takes me to understand the code.

#### 3 comments:

1. Nice work! The venerable 6800 and its brethren found their place in automotive applications, where the simple but flexible instruction set allowed it do do almost anything fairly well, as long as it didn't involve super high speeds. I found one the other day in a Buick LeSabre power seat controller, during a water damage diagnosis. I am aware that GM made frequent use of these processors in the 80s and 90s, although the personal computer market leaned much more heavily to the similar 6502, and the fancier Z-80 and 8086.

I am honored that you chose an excerpt from my rosettacode entry to illustrate your desire to implement label generation in your dis-assembler.

1. Thanks for the kind words and the interesting info. I grew up on a C<64 so have some familiarity with the derivative MOS 6510. I was quite glad to find your excellent rosettacode.org site and had to share it. :)

2. Rosettacode.org is most definitely NOT mine ... I'm just a junior member who likes to tinker around in a few of the easier tasks. There are some heavy hitters over there sharing some very complex and powerful pieces of code ... _way_ out of my league!

I _am_ proud of one of my ascii art designs. It is also 100% valid c++ code, which can be copied and pasted to http://codepad.org/ to put it through its paces:

http://rosettacode.org/wiki/99_Bottles_of_Beer#Bottled_Version

A thoroughly enjoyable waste of about nine hours of my life!

Cheers!

Note: Only a member of this blog may post a comment.