
Reverse engineering del firmware dei generatori di funzioni Philips PM5139 / PM5138A / PM5136: emulatori 8051 utilizzati come strumenti di misura, 35 sezioni di hardware documentato e un firmware V2.0 corretto
Un generatore di funzioni da 20 MHz del 1994 circa, smontato via software: due dump di EPROM, un emulatore 8051 usato come strumento di misura, e 35 sezioni di documentazione in cui ogni singola affermazione è supportata da un indirizzo nel listing, da una misura dell'emulatore o dallo schema elettrico.
Alla fine c'è un firmware V2.0 che corregge un difetto che Philips aveva distribuito, sei forme d'onda arbitrarie nostre, e un simulatore da browser che esegue la ROM originale istruzione per istruzione.

Ogni tabella di forme d'onda nella EPROM di programma, tracciata direttamente dal binario. In basso a destra c'è quella che ha dato inizio alla parte più interessante di questo progetto.
Il Philips PM5139 è il modello top da 20 MHz di una famiglia di tre strumenti (PM5136 / PM5138A / PM5139). All'interno c'è un PCB80C652 — un core 8051 con I²C hardware — una EPROM di programma 27512, e sei assemblaggi analogici appesi a un bus seriale.
Non esiste un manuale di servizio del PM5139. La gente lo cerca nei forum dal 2010. Quello che esiste è il manuale del PM5138A, il suo modello gemello da 10 MHz, che è internamente quasi identico.
Quindi questo progetto è partito dall'altro capo: fare il dump della EPROM, e capire cosa fa il codice finché lo strumento non è compreso abbastanza bene da modificarlo.
Erano disponibili due versioni del firmware, V1.3 e V1.5, entrambe dump M27512 da 64 KiB.
| Disassemblaggio | completo per entrambe le versioni, ~23 000 righe, con riferimenti incrociati |
| Listing annotato | 147 routine denominate, 145 commenti di intestazione, 3 826 righe annotate |
| Documentazione | 35 sezioni, 4 600 righe, ogni affermazione documentata |
| Percorso del segnale | frequenza, ampiezza, offset, AM, FM, burst, simmetria, sweep — tutto calcolato e verificato rispetto al codice originale |
| Hardware | tutti i 10 strobe, il bus C, l'I²C con ogni partecipante, le porte, la tastiera, la manopola rotativa, la bitmap del display |
| Bit di stato | 75 su 128 con un effetto documentato |
| Diff tra versioni | V1.3 vs V1.5 è identica strutturalmente al 91,4 %; ogni modifica nominata |
| Emulatori | uno in Python, uno in JavaScript (~8 M istruzioni/s), più un simulatore da browser in singolo file |
| Firmware nostro | V2.0 — un difetto di fabbrica corretto, checksum gestito, verificato nell'emulatore e su hardware reale |
| Posizione | Tipo | Funzione |
|---|---|---|
| D301 | PCB80C652 | core 8051 con I²C hardware, 12 MHz |
| D306 | 27512 | EPROM di programma — la V1.3 occupa 0000h–AC70h |
| D310 | X28C64 | EEPROM arbitraria sul bus MOVX |
| D305 | PCF8570 | 256 byte di NVRAM con batteria tampone su I²C (A0h) |
| D304-A | PCF8576 | driver LCD su I²C (70h), buffer da 20 byte |
| D302-A | SAA3007 | encoder di tastiera, codificato in larghezza di impulso su una singola linea |
| D307 | 74HCT4514 | decodificatore di strobe — il numero di strobe sono i bit di indirizzo A8…A11 |
Il lato analogico è un bus C seriale: l'UART dell'8051 funziona in modalità
shift register, TXD è il clock, RXD il dato, e uno strobe decide quale
dei dieci shift register memorizza i byte. MOV DPH,#8nh seguito da
MOVX @DPTR,A attiva lo strobe n. Quella singola riga è la chiave dell'intera
sezione analogica.
Questa è la parte che vale la pena rubare per il vostro progetto.
Leggere un binario 8051 da 44 KB a occhio ti porta forse a un terzo del percorso. Tutto il resto è venuto eseguendo il codice originale e osservando cosa ne esce:```python
c = CPU(rom) for w in test_values: set_amplitude(c, w) c.call(0x0AAC) # the original routine, untouched print(w, c.ram[0x1C]) # the byte that goes out on STR9
Varia l'input, leggi l'output, verificalo rispetto all'ipotesi. Ha
funzionato per frequenza, ampiezza, offset, profondità AM, deviazione FM,
conteggio dei burst, simmetria ed entrambe le caratteristiche di sweep.
Ogni formula nella documentazione è accompagnata dai punti campione su
cui è stata verificata.
Tre affinamenti l'hanno reso davvero produttivo:
**Guarda il bus, non il display.** La sezione 15 misura cosa fa un bit di
stato al buffer del display, e 74 dei 128 bit sembrano non fare nulla. Ma
molti di essi non pilotano il display, pilotano gli *assemblaggi
analogici* — e quelli sono visibili solo come telegrammi sul C-bus.
Registrare `MOV SBUF,…` e il terminatore `MOVX @DPTR` ha portato il
conteggio dei bit documentati da 54 a 75.
**Premi i tasti, non modificare la RAM.** Impostare a mano un byte di RAM
produce stati che lo strumento non assume mai. Ci è costato due
conclusioni errate e un crash nella tabella dei comandi. Iniettare codici
di tasto reali attraverso l'SAA3007 emulato fornisce stati che il
firmware raggiunge effettivamente — ed è stata una scansione a forza
bruta su tutti i 256 codici di tasto a rivelare quale tasto attiva quale
handler.
**Sospetta prima del tuo emulatore.** Tre bug nel nostro core hanno
prodotto comportamenti "inspiegabili" del firmware: `ACALL` eseguito come
`AJMP`, un flag di carry ausiliario mancante (così `DA A` si comportava
male e il firmware sembrava contare in binario), e un interrupt della
tastiera raddoppiato. Ogni conclusione di quel periodo è stata
rimisurata in seguito.
---
## La strada percorsa
**Prima statica.** Un disassembler con una tabella completa degli
opcode, poi discesa ricorsiva con euristiche per le jump-table. Ha
prodotto 30 508 byte di codice e ha lasciato 13 637 byte non
giustificati.
**Poi dinamica.** Una trace run — avvio a freddo, tutti i 23 tasti del
pannello frontale, entrambe le direzioni della manopola, ogni modalità
operativa, 86 milioni di cicli — marcando ogni indirizzo effettivamente
eseguito. Confrontata con l'analisi statica, ha trovato esattamente
**una** area che la discesa aveva mancato, e 10 686 dei byte inspiegati
si sono rivelati essere cinque blocchi di tabelle noti.
**Poi gli schemi.** L'OCR del manuale di servizio è inutile per gli
schemi, ma le immagini delle pagine a 400 dpi sono eccellenti. Tagliate
in tessere sovrapposte, sono leggibili fino ai numeri dei pin. Sei fogli
sono stati letti in questo modo — e dove cinque tracce parallele
corrono a 90 pixel di distanza, l'ispezione a occhio è stata sostituita
da uno script (`lines.py`) che estrae i segmenti di linea dalla bitmap.