ZXDebugGUI / Fuse
Jak ZXDebugGUI mluví s Fuse přes STDIO
Když jsem začal dělat ZXDebugGUI, nechtěl jsem psát vlastní emulátor ZX Spectra. To by byla práce na roky a hlavně zbytečnost, protože Fuse (který roky používám) už emuluje Spectrum velmi dobře. Potřeboval jsem jen způsob, jak Fuse ovládat zvenku a dostat z něj stav počítače zpět do svého debuggeru.
Fuse je normálně samostatná aplikace. Spustíš ji, otevře se okno, v něm běží Spectrum a ovládáš to přes menu a klávesnici. Jenže já jsem potřeboval něco jiného. Chtěl jsem mít vlastní okno s registry, pamětí, disassembly, breakpointy a obrazovkou, ale samotné emulování nechat na Fuse. Takže vznikla jednoduchá dohoda: ZXDebugGUI bude ovládací panel a Fuse bude motor pod kapotou. GUI mu pošle příkaz, Fuse ho provede a pošle zpět, co se ve Spectru změnilo.
Příklad: kliknu v GUI na Step. GUI pošle Fuse „udělej jednu instrukci“. Fuse ji provede a odpoví „procesor teď stojí na téhle adrese, registry mají tyhle hodnoty, obrazovka vypadá takhle“.
Co je STDIO
STDIO je obyčejný standardní vstup a výstup programu. Nic magického. Program něco čte ze vstupu a něco píše na výstup. To je celé. Mohl jsem vše řešit přes sockets, named pipes nebo nějaké větší API, ale toto řešení mi přišlo funkční a popravdě i nejlepší. Spustím proces, zapíšu mu řádek textu, přečtu řádek odpovědi. Když se něco pokazí, dá se to pořád ještě rozumně ladit.
Jak vypadá zpráva
Příkazy jsou JSON. Ne proto, že by to bylo módní, ale protože se to dobře skládá, dobře čte a .NET i C s tím umí rozumně pracovat. Jeden řádek znamená jeden příkaz.
{"id":1,"command":"step"}
Tohle je příkaz číslo 1 a říká Fuse: proveď step. Fuse odpoví taky jedním řádkem.
V odpovědi je stejné číslo, aby GUI vědělo, k čemu odpověď patří.
Snapshot je stav Spectra v jednom okamžiku. Registry, kus paměti, disassembly, obrazovka, stav pásky, breakpointy. Prostě všechno, co GUI potřebuje překreslit.
{
"id": 1,
"success": true,
"snapshot": {
"machine": "ZX Spectrum 128K",
"backendStatus": "Paging: RAM page 0/7, ROM 0, screen 5, lock off"
}
}
Vypadá to jednoduše, ale FUSE se muselo "trošku" přiohnout...
Samotný Fuse takhle zvenku ovládat nejde. Je to emulátor s vlastním životem. Má okno, menu,
vlastní smyčku a čeká, že s ním bude pracovat uživatel. Já jsem ale potřeboval, aby čekal na příkazy
z mého programu.
Proto jsem do Fuse přidal nový režim. Zapíná se parametrem --zxdebug-stdio.
Když ten parametr nepoužiješ, Fuse se chová normálně (proto je povinný a z nastavení ZXDebugGUI nejde smazat).
fuse.exe --zxdebug-stdio
Co jsem ve Fuse ohnul
Nakonec se ukázalo, že to není tak jednoduché, jak jsem si plánoval. Fuse bylo potřeba na několika místech trochu přesvědčit, že teď nebude hlavní hvězda s vlastním oknem, ale backend pro debugger. Proto jsem na ZXDebugGUI pracoval a pak ho dal do šuplíku - nezdá se to, ale ale práce to byla opravdu úmorná...
Co GUI Fuse posílá
Žádné romány. Většinou krátký příkaz, který odpovídá tomu, co jsem právě kliknul v GUI.
| Příkaz | Co tím myslím |
|---|---|
| connect | Připoj se a pošli první stav. |
| step | Proveď jednu instrukci procesoru a zastav. |
| continue | Běž dál, ale vrať řízení GUI. |
| pause | Zastav emulaci. |
| reset | Resetuj Spectrum. |
| triggerNmi | Pošli do Spectra NMI. |
| loadFile | Načti snapshot, pásku, disk nebo ROM. |
| keyDown / keyUp | Stiskni nebo pusť emulovanou klávesu. |
| setRegister | Nastav hodnotu v procesoru, třeba SP, HL, nebo PC. |
| writeMemoryByte | Zapiš jeden bajt do paměti. |
| toggleBreakpoint | Přidej nebo zruš breakpoint. |
Co je snapshot
Fuse po příkazu neposílá jen „OK“, ve většině případů Fuse pošle snapshot, tedy aktuální stav emulovaného Spectra.
Registry
Hodnoty uvnitř procesoru Z80, například AF, BC, DE, HL, IX, IY a SP.
PC
Ukazatel, kde procesor právě stojí v paměti. Podle něj GUI ví, kterou instrukci má v disassembly zvýraznit.
Paměť
Řádky paměti, které vidím v memory panelu, včetně bank u 128K strojů.
Disassembly
Bajty v paměti přečtené jako instrukce procesoru.
Obrazovka
Obsah obrazovky, ze kterého GUI složí náhled.
Ne každá odpověď posílá všechno. Když jen posunu paměťové okno, nepotřebuju znovu posílat celou obrazovku.
Breakpointy
Breakpoint je ...... (chvilka napětí) ....... zastavení běhu programu. Já vím, že to víte, musel jsem to napsat... :)
| Typ | Kdy zastaví |
|---|---|
| Execute | Když procesor doběhne na zadanou adresu. Tohle je ten klasický puntík v disassembly. |
| Run to | Dočasný execute breakpoint. Zadám adresu, pustím běh a po zastavení se zase uklidí. |
| Podmíněný |
Taky stojí na adrese, ale zastaví jen při splněné podmínce. Třeba až procesor dojde
na $8000 a zároveň BC=$1234.
|
| Memory write | Zastaví ve chvíli, kdy program zapisuje na určitou adresu v paměti. Dá se ještě zpřesnit na konkrétní zapisovaný byte. |
| Frame | Zastaví na začátku nového video framu. Hodí se, když řeším časování obrazu a nechci lovit místo podle adresy v kódu. |
| Scanline | Zastaví při kreslení další viditelné řádky obrazovky. Tohle je užitečné hlavně u raster efektů, multicoloru a podobných věcí, kde rozhoduje přesné místo na obrazovce. |
Execute, podmíněné a memory-write breakpointy mají konkrétní adresu (může být definována i návěstím), takže je umím ukázat přímo v disassembly. Frame a scanline breakpointy jsou jiné. Ty nejsou navázané na instrukci, ale na časování obrazu, takže je GUI ukazuje v seznamu breakpointů a v menu.
Port $7FFD
U 128K Spectra se přes port $7FFD přepínají paměťové banky, ROM a obrazovka.
Samotné číslo na portu není moc přátelské, takže GUI ukazuje i rozpad na jednotlivé bity.
Hned vidím, která RAM banka je připojená, která ROM je aktivní a jestli je paging zamčený.
| Bit | Co říká |
|---|---|
| 0-2 | Která RAM banka je připojená nahoře v paměti. |
| 3 | Která obrazovka se používá: screen 5 nebo screen 7. |
| 4 | Která ROM je vybraná. |
| 5 | Zámek stránkování. Když se zapne, běžně už nejde stránkování měnit až do resetu. |
| 6-7 | Nepoužité bity. |
Funguje to?
Ano, podle mne velmi dobře, ale je mi jasné, že vypuštěním do světa se na problémy narazí. Ale např. dnes jsem řešil (po vydání NextPlayeru pro klasické ZX Spectrum 128k s EsxDosem), že se tento program špatně spustí, když ho spouštíte přes .tapein. Vzal jsem ZXDebugGUI, v nastavení zvolil parametry, aby mi interní FUSE startovalo s EsxDosem, dal breakpoint na adresu od které se spouští NextPlayer a snažil se přijít na to, včem byl zakopaný pes (po chvilce jsem zjistil, že je nastránkovaná špatná ROM a tak jakékoliv volání RST $08 končili velmi špatně)... ano, nevyužil jsem veškeré možnosti ZXDebugGUI, ale nahrání LST souboru, použití RUN TO breakpointu a zobrazení portu $7FFD s vysvětlením, že je nastránkovaná druhá ROM jsem problém celkem rychle odhalil... ;) Ještě si budu chvilku se ZXDebugGUI hrát a pak ho vypustím ven....




