Reverse Engineering Bosch ME9/MED9: Állapottartó változók implementálása az NVRAM tükör segítségével
A modern motorvezérlő egységek (ECU-k) szoftverének módosítása során – például egy többprogramos "Map Switcher" vagy ALS (Anti-Lag System) logika integrálásakor – az egyik leggyakoribb mérnöki kihívás az állapotok perzisztens tárolása. Ha azt szeretnénk, hogy az ECU a gyújtás levétele, majd ismételt ráadása után "emlékezzen" a kiválasztott programra, a memóriakezelés mélyebb megértésére van szükség.
Ebben a bejegyzésben a Ford Focus ST/RS (és számos más típus) Bosch ME9 architektúrájának példáján keresztül vizsgáljuk meg, miért elégtelen a hagyományos RAM használata, és hogyan integrálhatjuk egyedi változóinkat a gyári NVRAM (Non-Volatile RAM) menedzser folyamataiba.
A "Volatile" RAM és a Power Latch fázis korlátai
A legegyszerűbb szoftveres megoldás egy új funkció állapotának (pl. map_selector) tárolására egy fel nem használt külső RAM terület (pl. a 0x80xxxx címtartomány) kijelölése. Futásidőben (runtime) ez a megoldás hibátlanul működik.
A probléma a leállítási szekvenciában keresendő. A gyújtás (Terminal 15) lekapcsolása után az ECU egy úgynevezett Power Latch (utánkeringetés) fázisba lép, amely során a főrelét maga a vezérlő tartja behúzva, amíg be nem fejezi az adatmentési és diagnosztikai folyamatokat. Amikor a főrelé elenged, a processzor tápellátása megszűnik, és a hagyományos RAM területek tartalma törlődik. A következő indításkor a rendszer újra inicializálja a memóriát, így az egyedi állapotok elvesznek.
Miért kerülendő a közvetlen EEPROM írás?
A fenti probléma logikus megoldásának tűnhet a változók közvetlen írása a soros (általában SPI buszon kommunikáló, pl. 95640 típusú) EEPROM chipbe, minden egyes állapotváltozáskor. Ez a megközelítés azonban architektúrális szempontból hibás:
- Időzítés és blokkolás: Az EEPROM fizikai írási ciklusa milliszekundumokban mérhető. Egy valós idejű megszakítási (interrupt) rutinból indított SPI tranzakció megakasztja az ECU fő szálát, ami szinkronizációs hibákhoz vagy Watchdog resethez vezethet.
- Hardveres degradáció: Az EEPROM memóriacellák írási ciklusainak száma véges. Ha egy változó egy dinamikusan változó paraméterhez (pl. gázpedál állás) van kötve, a chip fizikai élettartama radikálisan lecsökken.
Az OEM Megoldás: A Bosch NVRAM Menedzser és a RAM Tükör
A Bosch szoftvermérnökei a fenti problémákat egy dedikált NVRAM Driver implementálásával kerülték meg. A rendszer a következőképpen operál:
- Boot fázis: Indításkor a rendszer kiolvassa az EEPROM tartalmát, és struktúrált blokkok formájában betölti egy gyors, belső RAM területre. Ezt nevezzük EEPROM Tükörnek (RAM Mirror, az ME9 esetében tipikusan a
0x7FDxxxtartományban). - Futásidő (Runtime): A működés során az operációs rendszer kizárólag ezt a RAM tükröt olvassa és módosítja (például itt frissülnek a lambda-adaptációs értékek). A fizikai EEPROM chip felé nincs kommunikáció.
- Shutdown fázis: A Power Latch alatt az NVRAM Menedzser végigiterál a blokkokon. Ha egy blokk RAM tükre módosult a boot óta, a driver kiszámítja a blokk új ellenőrzőösszegét (checksum), és kizárólag a módosult blokkokat írja vissza a nemfelejtő memóriába.
A memóriatérkép felépítését az EEPROM_Block_Descriptor_Table szabályozza. Ez a struktúra definiálja az egyes blokkok azonosítóját (Block ID), a fizikai EEPROM offsetet (nv_offset), a RAM tükör címét (mirror_offset), a gyári alapértékeket tartalmazó Flash címet (const_offset), a hosszt és a Checksum algoritmus típusát (flags).
Rendszerszintű integráció: Változók beágyazása a RAM Tükörbe
Az igazi mérnöki megoldás az, ha a saját változónkat "ráültetjük" egy létező, gyárilag menedzselt blokk RAM tükrére.
Reverse engineering során azonosítható egy megfelelő méretű blokk – például a Block ID 02, amelynek hossza 32 bájt, és a RAM tükre a 0x7FD7D8 címen kezdődik. Ha az egyedi map_selector változónkat ennek a blokknak egy fel nem használt (vagy kevésbé kritikus) offsetjére, például a 0x7FD7F3 címre irányítjuk, az alábbi folyamat megy végbe:
- A Map Switcher rutinunk felülírja a RAM tükör megfelelő bájtját (ez mindössze egyetlen processzor-órajelet vesz igénybe).
- Leállításkor a Bosch NVRAM Menedzser regisztrálja, hogy a 2-es blokk tartalma megváltozott.
- A driver automatikusan, a gyári szekvencia részeként elmenti a teljes blokkot – benne a mi változónkkal – az EEPROM-ba.
- A következő indításkor a beállításunk automatikusan betöltődik a RAM-ba.
A kritikus láncszem: A Runtime Checksum Frissítése
A fenti metódus egyetlen sebezhetősége a memóriablokkok integritás-ellenőrzése. A Bosch rendszerek ciklikus háttérfolyamatok (NVRAM consistency task) segítségével periodikusan validálják a RAM tükör tartalmát a blokk végén tárolt checksum alapján. Ha a kódunk módosítja a RAM tükröt, de a checksum elavult marad, a rendszer adatsérülést detektál, és a blokkot visszaállítja a Flash-ben tárolt alapértékekre (const_offset), törölve a módosításunkat.
A probléma elkerülése érdekében az érték módosításakor azonnal, inline frissíteni kell a checksumot. A ME9 specifikus ellenőrzőösszeg képlete a legtöbb standard blokk (így a 2-es blokk) esetében:
Ez a kettes komplemens (Two's Complement) aritmetika sajátosságai miatt PowerPC assembly-ben rendkívül hatékonyan, egy bitfordítással és a Block ID kivonásával implementálható saját patchként:
; Előfeltétel: A célváltozó módosítása megtörtént a RAM tükörben ; r13 = SDA Base (0x7FFFF0) ; A 2-es blokk (Block ID: 0x02) hossza 32 bájt (30 bájt adat + 2 bájt CHK) li r5, 0 ; Összeg (Sum) regiszter nullázása li r6, 0 ; Ciklus index nullázása subi r11, r13, 0x2818 ; A Block 2 RAM tükör báziscíme (0x7FD7D8) calc_loop: lbzx r8, r11, r6 ; Következő adatbájt betöltése add r5, r5, r8 ; Hozzáadás az összeghez addi r6, r6, 1 ; Index inkrementálása cmpwi r6, 30 ; Végigértünk a 30 adatbájton? blt calc_loop ; Ha nem, ugrás a ciklus elejére ; --- A Gyári Képlet Számítása: -(Sum + BlockID + 1) --- xori r5, r5, 0xFFFF ; 1. Lépés: Bitwise NOT (~Sum) subi r5, r5, 2 ; 2. Lépés: Block ID kivonása: -(Sum + BlockID + 1) ; --- Ellenőrzőösszeg Tárolása --- sth r5, 30(r11) ; A 16-bites Checksum kiírása
A Gyári Bosch Checksum Rutin Kielemezve
Ha visszafejtjük magát a Bosch firmware-t egy decompiler (pl. Ghidra vagy IDA) segítségével, megtalálhatjuk a gyári EEP_checksum kalkulátor rutint is. Bár a szintaxisa és a felépítése elsőre sokkal hosszabbnak és kaotikusabbnak tűnik a mi optimalizált patch-ünknél, a matematikai logika hajszálpontosan megegyezik.
Nézzük meg, mi történik a motorháztető alatt a képen látható gyári rutinban:
- Optimalizált Ciklus (Loop Unrolling): A gyári kód (
LAB_00033c10-nél) ahelyett, hogy bájtonként olvasna, kettesével olvassa fel a RAM tükör tartalmát (lbzu r12, majdlbzu r11). Ezzel a fordítóprogram a processzor utasítás-végrehajtását (pipeline) gyorsítja. A tiszta adatbájtok összege (Sum) végül azr8regiszterben gyűlik össze. - A Képlet kiszámítása: Amikor a ciklus véget ér, a gyári kód befejezi a matematikát, felhasználva a híváskor az
r3regiszterben kapott Block ID értéket:
LAB_00033c30: add r12, r8, r3 ; r12 = Sum (r8) + BlockID (r3) addi r12, r12, 0x1 ; r12 = (Sum + BlockID) + 1 neg r12, r12 ; r12 = -(Sum + BlockID + 1) -> Kettes komplemens (előjelváltás) sthx r12, r4, r7 ; A 16-bites eredmény kiírása (ez automatikusan levágja & 0xFFFF-re)
Mi a különbség a gyári kód és a mi patch-ünk között?
Kizárólag a negáció végrehajtásának módja! A gyári kód a beépített neg (Negate) utasítást használja, ami automatikusan elvégzi az előjelváltást. Mi azért használtunk xori (bitfordítás) és subi kombinációt a saját patchünkben, mert bizonyos szituációkban – amikor "ellopott" (stolen) utasításokat pótolunk – a logikai bitműveletek beillesztése biztonságosabb, mivel ezek kevésbé bolygatják meg a processzor kritikus állapotjelző (Carry) flag-jeit. Matematikailag a kettő tökéletesen megegyezik.
Ennek az egyetlen gyári rutinnak a 100%-os megértése nagyon fontos, mert ez garantálja, hogy az egyedi Map Switcher vagy ALS állapotainkra "emlékezzen" az ECU a motor leállítását követően is.
← VISSZA A LOGOKHOZ