[Dev/Disassembly] The beginners' guide to Evo ECU table lookups
#1
[Dev/Disassembly] The beginners' guide to Evo ECU table lookups
I've seen a bit of confusion lately regarding how table data is looked up in the Evo ECU (some of it from me ), so I decided to spend a bit of time actually reading through the table and axis lookup routines to see if I can formalize both the format of the tables and their headers for documentation purposes, and get a better idea of what was going on with a couple of wacky things in 96940011. (I'm still not entirely sure I understand what's going on there, but hey, at least this might be somewhat useful for others.)
This is purely targeted at new developers (non-developers won't know what the heck I'm talking about, and older developers will already know this). If you really care about this stuff, required reading is the SuperH software manual (PDF link); you might not be able to grok all of it at once, but you should definitely understand what registers are, how memory is accessed, and have a basic understanding of the SH2 assembly language. The disassembler of choice for those of us working on the Evo ECU is IDA Pro; you'll definitely need something like that if you want to make any sense of this.
So, here we go:
The beginners' guide to Evo ECU table lookups
EcuFlash provides an XML file for each ROM, which defines a bunch of tables (1D, 2D, and 3D) that can be edited by the user. Every 2D or 3D table is indexed by one or two "axis" values; it's how you look up values in the table. By way of example, the High Octane Fuel Map has both RPM and Load axes; to look up a value in the table, you first look up the current engine speed (RPM) and calculated load, and the AFR target value you're looking for is where the two intersect on the table. (2D works the same way, but there's only one dimension to worry about.)
The XML tells you where in the ROM the table and axis table data is located. Back to our example, the High Octane Fuel Map table in 96530006 is listed as being at address "33bd", with the "Engine Load" axis as being at address "68b0" and the RPM axis at address "6888". If you pull those up in IDA Pro, you'll see the data listed there, just as EcuFlash promised, but that's not the whole story; if you've ever done this, you'll notice that IDA doesn't have any code or data references listed for those ROM addresses. Every table, and every "axis table", has a header attached to it, just before the data an end-user of EcuFlash normally edits. That header data is used by a set of standard routines that every Evo ROM provides to assist in performing lookups, and the address of that header is what IDA ends up generating code and data references to.
By understanding how the ECU performs lookups in tables, it can help you to both understand when you're looking at code that performs table lookups (thus helping you locate new tables), and help you understand why you get back the values you do when the ECU performs a table lookup.
To get to the value in a table, you have to first look up the axis positions. The axis table header, just prior to the data itself, defines an address to store lookup results in, followed by the address of "current value" of that axis. For example, if the table axis is "RPM", second address is where the current vehicle RPM is stored in memory. The next word defines the number of elements in the axis, and then the axis data follows.
So, axis tables look like this:
For 2D tables, you perform one axis lookup; for 3D tables, you perform two. The result of each call to sub_CC6 is a value stored in that axis' "result address"; it's the position in the axis that most closely (rounding down) matches the current value of the axis (ie. the position on an RPM axis that most closely matches the current engine speed).
Once you have the axis positions back from sub_CC6, you need to look up the actual table value. For byte-width tables, you call sub_C28; for word-width tables, sub_E02.
The table header has some similarities to the axis header. The first byte (or word, for sub_E02) determines whether the table has two dimensions (0x2) or three (0x3). The second byte (or word) is a global "adder" that is added to any value returned from the table. Next, a long word describes where the position on the X-axis is stored in memory (returned from sub_CC6); in a 3D table, an additional long word is included, as well as a one-byte value denoting the length of a row. After that, the table data follows, either in word (for sub_E02) or byte (sub_C28) form.
So, tables look like this:
Just like with axis lookups, you set r4 to the address of the table you want to perform the look-up in. When control is returned, r0 contains an interpolated value based on how "close" the axis values were to a labelled position on the axis.
So, if you see calls to sub_CC6 when reading through your disassembly of a ROM, it's an indication that an axis lookup is being performed, and if you see calls to sub_C28 or sub_E02, there's a table lookup happening. Looking at the lines of code leading up to that for any assignments to register r4 will tell you where the axis or table headers are located. Normally, the code right after a call to sub_C28 or sub_E02 will assign r0 to some memory address (or eventually get there, by bouncing it around from register to register), which you can log via the MUT table if you want to keep an eye on the value, and can give you an idea of what other code that uses that value is doing (for example, code that deals with the result from a High Octane Fuel Map lookup is probably involved in fueling).
Hopefully, this can help some newer folks get started with understanding one of the most common activities ECU code participates in.
This is purely targeted at new developers (non-developers won't know what the heck I'm talking about, and older developers will already know this). If you really care about this stuff, required reading is the SuperH software manual (PDF link); you might not be able to grok all of it at once, but you should definitely understand what registers are, how memory is accessed, and have a basic understanding of the SH2 assembly language. The disassembler of choice for those of us working on the Evo ECU is IDA Pro; you'll definitely need something like that if you want to make any sense of this.
So, here we go:
The beginners' guide to Evo ECU table lookups
EcuFlash provides an XML file for each ROM, which defines a bunch of tables (1D, 2D, and 3D) that can be edited by the user. Every 2D or 3D table is indexed by one or two "axis" values; it's how you look up values in the table. By way of example, the High Octane Fuel Map has both RPM and Load axes; to look up a value in the table, you first look up the current engine speed (RPM) and calculated load, and the AFR target value you're looking for is where the two intersect on the table. (2D works the same way, but there's only one dimension to worry about.)
The XML tells you where in the ROM the table and axis table data is located. Back to our example, the High Octane Fuel Map table in 96530006 is listed as being at address "33bd", with the "Engine Load" axis as being at address "68b0" and the RPM axis at address "6888". If you pull those up in IDA Pro, you'll see the data listed there, just as EcuFlash promised, but that's not the whole story; if you've ever done this, you'll notice that IDA doesn't have any code or data references listed for those ROM addresses. Every table, and every "axis table", has a header attached to it, just before the data an end-user of EcuFlash normally edits. That header data is used by a set of standard routines that every Evo ROM provides to assist in performing lookups, and the address of that header is what IDA ends up generating code and data references to.
By understanding how the ECU performs lookups in tables, it can help you to both understand when you're looking at code that performs table lookups (thus helping you locate new tables), and help you understand why you get back the values you do when the ECU performs a table lookup.
To get to the value in a table, you have to first look up the axis positions. The axis table header, just prior to the data itself, defines an address to store lookup results in, followed by the address of "current value" of that axis. For example, if the table axis is "RPM", second address is where the current vehicle RPM is stored in memory. The next word defines the number of elements in the axis, and then the axis data follows.
So, axis tables look like this:
- A long word for the result address.
- A long word for the value to look up.
- A word for the length of the axis.
- A series of words, as long as the length, containing the axis data.
For 2D tables, you perform one axis lookup; for 3D tables, you perform two. The result of each call to sub_CC6 is a value stored in that axis' "result address"; it's the position in the axis that most closely (rounding down) matches the current value of the axis (ie. the position on an RPM axis that most closely matches the current engine speed).
Once you have the axis positions back from sub_CC6, you need to look up the actual table value. For byte-width tables, you call sub_C28; for word-width tables, sub_E02.
The table header has some similarities to the axis header. The first byte (or word, for sub_E02) determines whether the table has two dimensions (0x2) or three (0x3). The second byte (or word) is a global "adder" that is added to any value returned from the table. Next, a long word describes where the position on the X-axis is stored in memory (returned from sub_CC6); in a 3D table, an additional long word is included, as well as a one-byte value denoting the length of a row. After that, the table data follows, either in word (for sub_E02) or byte (sub_C28) form.
So, tables look like this:
- A byte (or word, for word-sized tables) for the number of dimensions: 2 = 2D, 3 = 3D.
- A byte (or word, for word-sized tables) for a value "added" to all values returned from the table.
- A long word for the position on the X-axis.
- Optionally, a long word for the position on the Y-axis in a 3D table.
- Optionally, a byte (or word, for word-sized tables) for the length of each row in a 3D table.
- A series of words or bytes containing the table data.
Just like with axis lookups, you set r4 to the address of the table you want to perform the look-up in. When control is returned, r0 contains an interpolated value based on how "close" the axis values were to a labelled position on the axis.
So, if you see calls to sub_CC6 when reading through your disassembly of a ROM, it's an indication that an axis lookup is being performed, and if you see calls to sub_C28 or sub_E02, there's a table lookup happening. Looking at the lines of code leading up to that for any assignments to register r4 will tell you where the axis or table headers are located. Normally, the code right after a call to sub_C28 or sub_E02 will assign r0 to some memory address (or eventually get there, by bouncing it around from register to register), which you can log via the MUT table if you want to keep an eye on the value, and can give you an idea of what other code that uses that value is doing (for example, code that deals with the result from a High Octane Fuel Map lookup is probably involved in fueling).
Hopefully, this can help some newer folks get started with understanding one of the most common activities ECU code participates in.
Last edited by logic; Sep 24, 2009 at 08:45 AM. Reason: Added table spec notes for word-width tables.
#3
Evolved Member
iTrader: (17)
Its awesome you're doing this, though I highly suggest screenshots with labeling to become immensely easier for users to 'paint the picture' in their heads as they read. Two thumbs up though. I haven't tried disassembling in over a year but I'd love there to be a tutorial so I can get further with it.
#4
Evolved Member
iTrader: (4)
Can I ask what language the ROM is written in. I did take some Language Engineering classes for C++, Visual Basic, Java, Cobol just to name a few, and I havent quite figured it out yet. No pro by any meens, just curious so I can dive in a little.
EDIT: It almost seems as the though the stock ECU is an ARRAY, or a cube. Several little storage addresses. I see there are reference pointers to locations in addresses, that will hold data, and then ECU Flash edits that or calculates utilizing that, etc.
So is it more database and Query when you dissasemble? SQL and databases were not my strong point when it came to these pointers, reference pointers, arrays, etc.
Thanks again.
EDIT: It almost seems as the though the stock ECU is an ARRAY, or a cube. Several little storage addresses. I see there are reference pointers to locations in addresses, that will hold data, and then ECU Flash edits that or calculates utilizing that, etc.
So is it more database and Query when you dissasemble? SQL and databases were not my strong point when it came to these pointers, reference pointers, arrays, etc.
Thanks again.
Last edited by Raceghost; Sep 23, 2009 at 09:52 PM.
#5
EvoM Guru
iTrader: (6)
Logic - there are byte AND word tables:
byte tables have byte sized header fields and data, word tables have word sized header field and data.
also after the 2 or 3 header field for a 2d or 3d table there is a additive value, this is used for the timing tables - ie 0x14 = +20 onto whatever data is looked up.
byte tables have byte sized header fields and data, word tables have word sized header field and data.
also after the 2 or 3 header field for a 2d or 3d table there is a additive value, this is used for the timing tables - ie 0x14 = +20 onto whatever data is looked up.
#6
Evolved Member
iTrader: (22)
EDIT: It almost seems as the though the stock ECU is an ARRAY, or a cube. Several little storage addresses. I see there are reference pointers to locations in addresses, that will hold data, and then ECU Flash edits that or calculates utilizing that, etc.
So is it more database and Query when you dissasemble? SQL and databases were not my strong point when it came to these pointers, reference pointers, arrays, etc.
Thanks again.
So is it more database and Query when you dissasemble? SQL and databases were not my strong point when it came to these pointers, reference pointers, arrays, etc.
Thanks again.
The real thing you need here is time and patience and you will be able to contribute as well. I only have the time when I am on vacation and then I don't have the patience .
Last edited by codgi; Sep 23, 2009 at 11:28 PM.
#7
Evolved Member
iTrader: (22)
Logic - there are byte AND word tables:
byte tables have byte sized header fields and data, word tables have word sized header field and data.
also after the 2 or 3 header field for a 2d or 3d table there is a additive value, this is used for the timing tables - ie 0x14 = +20 onto whatever data is looked up.
byte tables have byte sized header fields and data, word tables have word sized header field and data.
also after the 2 or 3 header field for a 2d or 3d table there is a additive value, this is used for the timing tables - ie 0x14 = +20 onto whatever data is looked up.
Trending Topics
#8
I spent quite a bit of time searching, and didn't see any axis tables that weren't word-length, and didn't notice any byte-sized code for reading them; as far as you know, are there any non-word-width axis tables? I just assumed they would have added a routine for that to go along with the ones for parsing the main tables, but I couldn't find it.
Kind of. It's basically just a number that's added to any value looked up in the table prior to return. (Hmm, I need to check the assembly again to see if that's a signed value or not; if it is, it would mean you could subtract as well.)
#10
Honestly, this has me thinking that we really need an "intro to disassembly" topic, or series of topcs. Something that incorporates what acamus' onload.idc script does automatically for you (generating disassembled code from the interrupt vector table and from various jump tables in Mitsu's code; labeling registers, the MUT table, and other well-known chunks of code and memory; creating RAM/ROM/register segments; etc) along with some basics on SH assembly (stuff that's different from what people might have traditionally run into, like how delayed branching affects instruction execution order) and maybe some discussions of the hardware (how Mitsu uses the SCI interface, etc).
Teaching assembly language is a little out-of-scope, but even if you've worked in a version of assembly before, you might not have worked with an embedded system like this, which makes it a little like learning a new language. (That was my problem originally; I'd done plenty of 6502/65C02 assembly as a child, and a bit of x86 assembly here and there, but never on a small platform like this where the hardware is exposed as directly as it is; there was always an OS for me to interact with instead.)
My main interest here is in helping more people get involved in this stuff in a substantial way. When someone like jcsbanks gets tired of his Evo and moves on to a more interesting project, we end up with a large knowledge gap that others have to step in and fill, and there's not a lot of "others" right now (imagine if tephra had decided to get a nice reliable econocar after his IX accident, rather than getting an X). Without new people getting interested in how the ECU works, new development is eventually going to slow down or stop as the "early adopters" move on to other projects.
(RomRaider/Enginuity is a great example of that problem, you just don't see a lot of new development happening anymore; at least, not in public, and certainly not a lot of ECU-side code development. Mind you, they run a higher risk of bricking their ECUs if something isn't quite right, which raises the bar for people who want to get involved; we're pretty lucky in that respect, it's tough to render our ECUs unbootable. )
#12
Do you know if there's a byte-sized version of the axis table lookup routine?
I'd really like to see all the common routines and data up to 0x1500 documented better; since they're exactly the same across all VII through IX roms, it'd be a big leg up for someone new getting up to speed.