B_CALL ($ADBE)
Fout. Fout, fout, fout,
FOUT.
Hoewel ik het leuk vind dat je tijdens je middelbare school-tijd al zo met programmeren bezig bent dat je Z80 assembly probeert, lijkt het erop dat je een paar details nog niet helemaal begrepen hebt. Er bestaat namelijk helemaal geen "B_CALL" instructie (voor de rest van de lezers: waarschijnlijk "base call", al zou ik het "system call" genoemd hebben). De opcode (tweede regel, meteen na de marge in het midden) 0xEF heet normaal gesproken "rst 28h" en is in feite hetzelfde als "call 28h". Het enige verschil is dat deze opcode één byte lang is (er zijn acht rst opcodes: 0xC7 = rst 00h, 0xCF = rst 08h, ..., 0xFF = rst 38h).
Het oorspronkelijke idee is dat je dit kunt gebruiken als interrupt vectors (in "interrupt mode 0" wordt na het triggeren van een interrupt een byte van de databus gelezen en uitgevoerd als opcode; aangezien normale calls altijd een 16-bit adres nodig hebben als bestemming kun je die niet gebruiken, dat past niet). Juist omdat deze opcodes slechts één byte lang zijn en een (zeer bescheiden) variatie in bestemming hebben is dit mogelijk. Het ene stuk hardware kan dus bijvoorbeeld interrupten met een "rst 08h" terwijl een andere "rst 10h" kan gebruiken.
Door de trucendoos die TI heeft opengetrokken om, aanzienlijk, meer dan 64 kB aan geheugen in een 16-bit adresruimte te krijgen is het overgrote deel van het geheugen op een TI 84 niet rechtstreeks adresseerbaar. Onder andere het OS zelfs is normaal gesproken niet "gemapped". Als de processor een rst 28h tegenkomt gebeurt het volgende:
- De Z80 voert de rst 28h instructie gewoon uit (een rst hoeft niet persé van de databus te komen na een interrupt, gewoon in de code voorkomen mag ook, een mogelijke toepassing zou exception handling kunnen zijn). Oftewel: push het adres van de volgende instructie op de stack en vervang de instruction pointer door 0x0028.
- De routine die hier staat leest de twee bytes ná de rst (met behulp van de stack pointer, die hiernaar wijst) en slaat deze op.
- In normale Z80-code is de byte na een rst meteen de volgende instructie, in "TI Z80" volgen twee data bytes (normaal gesproken in het bereik 0x4000 - 0x7FFF) en daarna pas de volgende instructie. Daarom wordt het return address (bovenste waarde op de stack) met twee opgehoogd.
- Er wordt een speciaal stuk geheugen adresseerbaar gemaakt (op 0x4000 - 0x7FFF) met daarin een tabel. De zojuist opgezochte waarde wordt gebruikt om daarin een waarde op te zoeken. Effectief komt dit neer op een 24-bit adres.
- Dit adres wordt eerst adresseerbaar gemaakt, daarna wordt het gecalled (en wordt de daadwerkelijke functie uitgevoerd). Zodra de routine klaar is wordt teruggekeerd naar het laatste stukje van de rst 28h code.
- De oorspronkelijke layout van de address space wordt hersteld, waarna geRETurned wordt naar het adres bovenop de stack; twee bytes ná de 0xEF opcode.
Omdat dit mechanisme veelvuldig wordt gebruikt (en om die twee extra bytes netjes in de code te voegen) heeft TI het hele B_CALL gebeuren bedacht, wat gewoon een macro is die door de assembler wordt omgezet. Die twee extra bytes zorgen er wel voor dat je TI 84 code niet door een gewone Z80 disassembler moet halen; die weet niks van deze truc en zal die extra bytes ook proberen te vertalen naar opcodes. Doordat Z80 opcodes verschillende lengtes kunnen hebben zit het er dik in dat je daarna het begin van de volgende opcode op de verkeerde plaats verwacht en het helemaal in het honderd loopt.
Bijkomend voordeel is dat TI in elke update van zijn OS alle routines op een andere plaats in het geheugen onder kan brengen door simpelweg de tabel te updaten.
Overigens, als je even naar het begin van de code kijkt:
ex de,hl
inc b
xor a
jp nz,$A3BF
add a,c
call pe,256
Waarom zou je registers
de en
hl omwisselen als eerste instructie, er staat nog niets in.
Een
xor a (effectief
ld a,0 en het bijwerken van het
f (flags) register) meteen gevolgd door een
jp nz, xxxx slaat nergens op: waarom testen op nz (not zero) als de voorgaande instructie de zero flag altijd reset. Ook is dit stukje code op geen stukken na lang genoeg om adres 0xA3BF te bevatten.
Op zich is er niks mis met
call pe, xxxx, maar de pe (parity even) conditie is extreem zeldzaam in alle Z80-code die ik ooit gezien heb. (Oh en 0x0100 valt ook buiten dit stukje code; het zijn maar 160 bytes.)
Dus die eerste paar regels zouden je al moeten vertellen dat het nagenoeg uitgesloten is dat het hier Z80-code betreft.
ps. Zie net dat hieronder een paar keer op het voorkomen van 0xDEADBEEF wordt gewezen. De laatste drie bytes daarvan (in LSB) zijn precies die
B_CALL 0xBEAD (
rst 28h \ 0xBEAD). Zit ik speciaal naar die 0xEF te zoeken, kijk ik er nog overheen.
[Reactie gewijzigd door robvanwijk op 23 juli 2024 09:58]