CoBra - Sistem de operare CP/M (CHRIS MDSP 1992) cu 40/64 coloane text vizibile pe ecran simultan
1. Modul de instalare a sistemului de operare
Pentru instalarea acestei versiuni de CP/M pentru CoBra sînt necesare două fișiere, SP.COM și MD.COM. Utilitarul SP.COM instalează codul sistemului în pistele 0 și 1, iar MD.COM copiază utilitarele de bază (CU.COM, D.COM, DIP.COM, FDD.COM, POWER.COM, RUN.COM) în zona de date a discului, cu obișnuitul truc de ascundere a codului CP/M Loader în ultimul sector alocat pentru fișierul D.COM. Aceste două utilitare par a fi scrise de același Pîrvu Cristinel-Dan menționat la versiunea CP/M discutată anterior (Chris Inst). O facilitate iarăși introdusă de el în premieră la CP/M CoBra este opțiunea de a specifica o comandă utilizator care să se execute automat la lansarea CP/M. Această comandă se poate specifica la rularea executabilului SP.COM, dar pentru ca opțiunea să fie disponibilă executabilul trebuie rulat cu parametrul CHRIS++ (adică "SP CHRIS++").
De fapt ar fi cam două posibilități de instalare a acestei versiuni CP/M:
- Una ar fi rularea SP.COM și MD.COM în orice ordine (ordinea nu contează din moment ce SP.COM acționează numai asupra pistelor 0 și 1, iar MD.COM asupra pistelor de date (2-6).
- A doua ar fi rularea executabilului SP.COM și, separat, copierea fișierului LK.SYS (CP/M Loader-ul standard CoBra) pe discheta care trebuie făcută sistem. Discul sistem astfel obținut nu va mai conține utilitarele de bază, dar acestea se pot copia oricînd separat.
În continuare am să descriu ambele procedee de instalare.
Pentru început, un sistem CP/M (evident diferit, plecînd de la ipoteza că instalăm pentru prima dată acest sistem) va trebui încărcat, pentru a lansa comenzile necesare, dar înainte de orice se impune un comentariu foarte important:
Fiecare versiune de CP/M are o cantitate limitată de memorie disponibilă pentru programe utilizator. Pentru a garanta execuția corectă a unui program, trebuie ca dimensiunea fișierului executabil al programului să se încadreze în limita de memorie totală disponibilă a sistemului. În cazul de față, utilitarul MD.COM (40 KB) ar fi suficient de mic pentru limita de 53 KB de memorie disponibilă în sistemul CP/M cu 80 de caractere pe linie pe care eu l-am împachetat în SYSTEM ROM. Dar pentru orice eventualitate am să pornesc cu cel cu 40 coloane text vizibile (SYSGEN 40c) deja analizat anterior în detaliu.
Prototipul meu CoBra are o unitate fizică de floppy de 3.5" instalată ca unitate fizică 0 şi un emulator fizic de unitate floppy instalat ca unităţile fizice 2 şi 3 (emulatorul emulează 2 unităţi fizice diferite).
Aşa că am folosit emulatorul pentru a lucra cu unităţile 2 şi 3, am confecţionat o imagine floppy conţinînd fişierele necesare menţionate mai sus şi am folosit-o ca unitate 3 (D:), şi am pornit un sistem CP/M cu 40 coloane text (ITC Brasov 1989) de pe o imagine de dischetă sistem (SYSG40_CL_SYSDISK.hfe), generată conform secțiunii „SYSGEN 40c” a acestui website, pe care am montat-o ca unitate 2 care la încărcarea sistemului a devenit unitate logică A:.
Instalare folosind utilitarele SP.COM și MD.COM |
1: | Am lansat sistemul CP/M cu 40 coloane text de pe unitatea 2 care a devenit logică A:. |
|
2: | Am tastat comanda de lansare a generatorului de sistem de pe unitatea logică D:, cu parametrul CHRIS++ pentru a putea introduce ulterior o comandă pentru autorun. Fără acest parametru, opțiunea nu este disponibilă. |
|
3: | După un ENTER, ecranul se schimbă și programul cere introducerea unei comenzi pentru autorun. Neavînd nevoie de așa ceva pentru cazul de față am apăsat pur și simplu ENTER. |
|
4: | Programul cere selectarea unității fizice conținînd discul care trebuie făcut sistem. Am tastat 2. |
|
5: | Apare un mesaj care cere introducerea discului destinație în unitatea 2. Am schimbat imaginea montată în emulator ca unitate 2 cu o imagine de dischetă formatată (goală) și apoi am apăsat ENTER. |
|
6: | Programul transferă codul sistemului în pistele 0 și 1 după care întreabă dacă se dorește repetarea operației. Tastez N întrucît nu e cazul. |
|
7: | Execuția programului se termină prin ieșire pa promptul CP/M. |
|
8: | Tastez comanda de lansare a celuilalt executabil (MD.COM) de pe unitatea logică D: |
|
9: | După un ENTER este afișat mesajul de început și se cere selecția unității conținînd discul destinație. Aici programul așteaptă specificarea unității logice, așa că în loc de 2 tastez A. |
|
10: | Apare un prompt de introducere a discului destinație în unitatea A:. |
|
11: | Discul este deja introdus așa că apăs ENTER. Utilitarele de bază sînt copiate după care programul întreabă dacă se dorește repetarea operației. |
|
12: | Tastez N și ies înapoi la promptul CP/M. |
|
|
|
Instalare folosind utilitarul SP.COM și fișierul LK.SYS (CP/M Loader standard CoBra) |
1: | Am lansat sistemul CP/M cu 40 coloane text de pe unitatea 2 care a devenit logică A:. |
|
2: | Am tastat comanda de lansare a generatorului de sistem de pe unitatea logică D:, cu parametrul CHRIS++ pentru a putea introduce ulterior o comandă pentru autorun. Fără acest parametru, opțiunea nu este disponibilă. |
|
3: | După un ENTER, ecranul se schimbă și programul cere introducerea unei comenzi pentru autorun. Neavînd nevoie de așa ceva pentru cazul de față am apăsat pur și simplu ENTER. |
|
4: | Programul cere selectarea unității fizice conținînd discul care trebuie făcut sistem. Am tastat 2. |
|
5: | Apare un mesaj care cere introducerea discului destinație în unitatea 2. Am schimbat imaginea montată în emulator ca unitate 2 cu o imagine de dischetă formatată (goală) și apoi am apăsat ENTER. |
|
6: | Programul transferă codul sistemului în pistele 0 și 1 după care întreabă dacă se dorește repetarea operației. Tastez N întrucît nu e cazul. |
|
7: | Execuția programului se termină prin ieșire la promptul CP/M. |
|
8: | Resetez calculatorul și lansez sistemul CP/M din SYSTEM ROM cu unitatea logică C: (fizică 2) ca unitate implicită (conținînd discul destinație). |
|
9: | Tastez comanda de copiere a fișierului LK.SYS de pe unitatea D: pe unitatea C: |
|
10: | După un ENTER operația se execută succes. Acum discheta din unitatea fizică 2 este gata de bootare cu noul sistem instalat. |
|
|
|
2. Descrierea sistemului de operare
Acest sistem (în forma lui originală) este rulabil NUMAI de pe unitățile fizice 2 și 3. Mai exact, dacă se folosesc unități de disc dublă densitate, nu vor putea fi folosite cu acest sistem de operare decît ca unități fizice 2 și/sau 3. Unitățile fizice 0 și 1 sînt presupuse a fi simplă densitate, cu alt număr de sectoare pe pistă. Codul din BOOT ROM știe să încarce sistemul CP/M de pe orice unitate fizică (0-3), dar sistemul în sine, odată încărcat, nu va putea accesa discul de pe care a fost lansat decît dacă unitatea fizică respectivă are densitatea corespunzătoare (unitățile 0, 1 SD, unitățile 2, 3 DD).
Încărcarea lui se face din codul standard de BOOT CoBra în configurația hardware de 80 KB RAM, prin apăsarea tastei D.
Secvenţa de pornire a sistemului, aşa cum este afişată pe ecran la apăsarea tastei D din configuraţia de pornire, arată cam în felul următor:
(plimbaţi mouse-ul peste coloana din stînga, de sus în jos şi urmăriţi imaginea mare din dreapta)
Se observă logo-ul „PLUS”, care este generat din codul CP/M Loader-ului, care a fost și el modificat de către Cristinel-Dan în acest scop. Bănuiesc că el este primul care a venit cu această idee, foarte interesantă în sine.
Acest sistem este o versiune modificată a celei create de echipa CoBra de la ITC Brașov, cu multe facilități extrem de utile. Cu tasta PG EDT se poate activa și dezactiva sunetul generat la apăsarea tastelor. Recunoaște tasta F1 ca avînd funcția de BackSpace. Cu combinația Symbol Shift + Esc se poate reseta calculatorul înapoi în BOOT ROM. Tasta DEL se manifestă la fel ca în versiunea originală a CP/M CoBra, apăsarea ei la sfîrșitul unui număr de caractere introduse la promptul CP/M șterge ultimul caracter introdus din buffer-ul consolei, dar nu-l șterge și de pe ecran ci în schimb îl dublează, tipărindu-l din nou.
Pe ecran este afișată o fereastră de 24 x 40 de caractere, care poate cuprinde jumătatea dreaptă sau cea stîngă a ecranului text virtual CP/M de 24 x 80 caractere. La un moment dat este vizibilă numai una din pagini. Afișarea se poate comuta între cele două jumătăți cu tasta F4. Cu tasta Graph Norm fereastra afișată se modifică la 64 coloane text, ceea ce înseamnă că la apăsarea tastei F4 se va obține o suprapunere parțială a afișării celor două jumătăți de ecran virtual CP/M cu 80 coloane text, coloanele de text de la mijloc fiind permanent vizibile și ușurînd citirea conținutului total afișat pe ecran.
Cristinel-Dan a mers chiar și mai departe cu îmbunătățirile. Imediat după încărcarea sistemului, la adresa $0100 este lăsat un cod care conține un fel de help și mic demo al facilităților pe care el le-a introdus în CP/M pentru CoBra. Acest cod se poate lansa cu comanda RUN, folosind un binecunoscut truc CP/M care presupune existența unui fișier de lungime zero salvat pe disc în prealabil. Practic fișierul există doar ca o intrare în director, neavînd conținut în zona de date a discului. Atunci cînd acest fișier (denumit RUN.COM) este lansat cu comanda RUN, CCP încearcă să-i încarce conținutul în memorie la adresa $0100, ca pentru orice alt executabil, dar neavînd ce încărca nu mai modifică zona de memorie de la $0100 în sus ci dă direct controlul codului dinainte existent la adresa $0100. În cazul de față, codul lăsat la adresa $0100 după încărcarea sistemului conține informațiile descrise mai jos:
1: | Am tastat comanda RUN. |
|
2: | După un ENTER apare o primă pagină de informații cu tastele speciale recunoscute de sistem. |
|
3: | După alt ENTER, apare o a doua pagină, cu numere de funcții BIOS suplimentare pentru primitive grafice |
|
4: | După încă o tastă se lansează o mică animație care demonstrează folosirea acestor funcții grafice. |
|
5: | Am capturat cîteva cadre din această animație pentru a da o idee despre efect. Deocamdată nu am cu ce să capturez o secvență video continuă. |
|
6: | ... |
|
7: | ... |
|
8: | ... |
|
9: | ... |
|
10: | ... |
|
11: | ... |
|
12: | ... |
|
13: | ... |
|
14: | ... |
|
15: | ... |
|
16: | ... |
|
17: | ... |
|
18: | Animația se poate opri cu orice tastă... |
|
19: | după care sînt afișate numele și numărul de telefon ale autorului acestor facilități suplimentare. |
|
|
|
Deci acest sistem are chiar și cîteva funcții primitive grafice introduse de Cristinel-Dan. Destul de impresionant, ca dealtfel și demo-ul grafic animat!
Pentru determinarea hărții memoriei ocupate de sistem, am început prin a salva conținutul memoriei dintre $0100-$FFFF (cu comanda „SAVE 255 MEMDUMP.BIN”) și a lista conținutul zonei de memorie $0000-$00FF cu utilitarul POWER.
Conținutul primei pagini de memorie listate cu POWER
(snapshot-uri ale celor 2 jumătăți de ecran cu 40 coloane text accesibile cu tasta F4, redînd întregul conținut al ecranului text virtual CP/M de 80 coloane text) |
1: | |
2: | |
Din listingul obținut se vede că adresa de început a modulului BIOS este $F600 (conținutul locațiilor $0001 și $0002, minus 3).
De asemenea, adresa de început a modulului BDOS este $E806 (conținutul locațiilor $0006 și $0007).
Deci memoria liberă disponibilă programelor utilizator (mărimea zonei TPA) ar fi cam $E800-$0100=$E700 adică 57.75 KB |
|
|
0000: C3 03 F6 80 00 C3 06 E8 00 00 00 00 00 00 00 00
0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0050: 00 00 00 00 00 00 00 00 00 00 00 00 01 3F 3F 3F
0060: 3F 3F 3F 3F 3F 3F 3F 3F 00 00 00 74 00 20 20 20
0070: 20 20 20 20 20 20 20 20 00 00 00 00 00 00 00 00
0080: 00 42 4C 4F 4F 44 59 20 20 43 4F 4D 01 00 00 26
0090: 5C 01 5D 01 5E 01 00 00 00 00 00 00 00 00 00 00
00A0: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5
00B0: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5
00C0: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5
00D0: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5
00E0: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5
00F0: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5
|
|
3. Studiul low-level și modificarea sistemului de operare
Am început studiul acestui sistem (prin dezasamblare) în principal cu două scopuri:
- Pentru a-l face rulabil din piste sistem cu format standard (identic cu restul pistelor, 2-79)
- Pentru a-l face să lucreze cu toate cele patru unități fizice posibile (0, 1, 2, 3) pe post de unități DSDD 720K cu 80 piste, 9 sectoare/pistă, 512 octeți/sector
Cu toate că discheta este formatată (cu utilitarul FDD.COM) la început cu același format pe toate pistele (9 sectoare pe o față de pistă, 512 octeți/sector), atunci cînd se rulează programul generator de sistem (INSTALL.COM), acesta, înainte de a scrie sistemul în pistele 0 și 1, formatează aceste două piste cu un format non-standard, cu cîte un singur sector pe față de pistă, cu 4096 octeți/sector. Pentru a complica lucrurile și mai mult, codul sistemului de operare este scris în pistele sistem în oglindă (de la coadă la cap) pentru ca nu cumva cineva să se poată uita prea ușor la cod și să-l dezasambleze ca să vadă marile „secrete de stat”.
Conținutul pistelor sistem, extras cu software-ul emulatorului HxC |
|
Așa că pentru a scăpa de această „protecție” am decis să transpun sistemul în pistele sistem formatate cu același format ca și restul dischetei. Astfel, o imagine RAW de dischetă sistem ar putea fi manipulată mai ușor cu un eventual utilitar, care să poată trata și codul din pistele sistem la fel cum tratează codul din zona de date a dischetei. În plus, copierea sistemului pe altă dischetă s-ar putea face mult mai ușor, cu utilitarul FDD.COM, prin folosirea opțiunii „Duplicate”, copiind pur și simplu primele 2 piste (0 și 1) și apoi copiind CP/M Loader-ul extras din sectorul ascuns la sfîrșitul utilitarului D.COM, sau (funcțional echivalent) fișierul LK.SYS (CP/M Loader-ul original CoBra).
Cum am făcut asta:
Pentru început am generat o imagine RAW floppy goală (formatată) de 720 KB cu numele TEST_CHRSMD.img. Asta se poate face în mai multe moduri, unul ar fi cu utilitarul cobra-cpm-diskimg-copy.c scris de mine, altul ar fi cu software-ul emulatorului floppy HxC. Sau, pentru utilizatori Linux înrăiți (ca mine), pur și simplu cu comanda bash:
dd if=/dev/zero bs=1k count=720 | tr '\000' '\345' > TEST_CHRSMD.img
întrucît avem 80 piste x 18 sectoare x 512 octeți = 720 KB.
Am convertit apoi imaginea RAW în imagine HFE folosind HxCFloppyEmulator.exe, am pus-o pe un SD Card și în emulator am instalat sistemul pe ea cu procedeul descris la începutul acestei pagini.
Am scos apoi SD Card-ul din emulator și am copiat înapoi în Linux imaginea TEST_CHRSMD.hfe. Am încărcat-o în HxCFloppyEmulator.exe:
și am deschis fereastra Track Analyzer selectînd pista 0 fața 0, ca în imaginea de mai jos:
Se poate vedea clar că pe fața 0 a pistei 0 există un singur sector, de 4096 octeți, iar prin punerea cursorului de mouse deasupra zonei verzi (zona sectorului propriu-zis), apare în dreapta jos întreg conținutul sectorului, listat în mod text (hexa) cu tot cu informațiile de formatare aferente. Am selectat cu mouse-ul întregul text, după cum se vede în următoarea imagine, și l-am extras într-un fișier separat, și am repetat operația pentru ambele fețe ale ambelor piste sistem (0 și 1). Am obținut astfel listingul prezentat mai înainte al conținutului pistelor sistem.
Așadar se vede clar că formatul pistelor sistem a fost modificat de către generatorul de sistem. Pentru comparație, în imaginea de mai jos se poate vedea cum arată pista 2 fața 0 (conținînd prima parte a directorului), care are 9 sectoare, numerotate de la 1 la 9, a cîte 512 octeți fiecare:
Apoi am exportat imaginea floppy (cu butonul ”Export” din fereastra principală a HxCFloppyEmulator.exe) în format RAW cu numele TEST_CHRSMD.img. În acest format, pistele sistem ocupă zona de început a fișierului de 4 x 4 KB = 16 KB, după care urmează zona de director. Întrucît imaginea dischetei nu conține nimic altceva decît sistemul proaspăt instalat, directorul va conține o singură înregistrare (32 octeți), în prima poziție din director, pentru fișierul LK.SYS copiat după instalarea sistemului în pistele 0 și 1.
Am extras datele din pistele 0 și 1 într-un fișier separat, numit CHRSMD_SYS_REV.bin, cu comanda bash:
head -c 16K TEST_CHRSMD.img > CHRSMD_SYS_REV.bin
Acest fișier are lungimea de 16 KB, adică 2 piste x 2 fețe x 1 sector/față x 4096 octeți/sector.
Folosind utilitarul file-reverse.c scris de mine (prezentat pe pagina „CoBra CP/M - Diverse chestii”), am întors pe dos codul extras și l-am salvat ca CHRSMD_SYS.bin, obținînd astfel codul sistem în ordinea corectă:
./file-reverse CHRSMD_SYS_REV.bin
mv CHRSMD_SYS_REV.bin-rev CHRSMD_SYS.bin
Apoi, cu comanda:
dd if=/dev/zero bs=1k count=2 | tr '\000' '\345' > E5fill.bin
am generat un bloc de octeți $E5 de 2 KB (E5fill.bin) pe care apoi l-am atașat la sfîrșitul fișierului CHRSMD_SYS.bin, cu comanda de mai jos, obținînd un bloc de 18 KB (CHRSMD_SYS_Tracks_full.bin) conținînd codul sistemului urmat de o zonă formatată, care bloc are exact dimensiunea a două piste disc normale pentru acest tip de CP/M (2 piste x 2 fețe x 9 sectoare/față x 512 octeți/sector = 18 KB):
cat CHRSMD_SYS.bin E5fill.bin > CHRSMD_SYS_Tracks_full.bin
Deci în acest moment am obținut pistele sistem în format normal, cu codul sistemului CP/M cu 40 coloane text vizibile simultan, scris în ordinea normală.
Mai departe, pentru a obține o imagine completă de dischetă sistem bootabilă, este nevoie de generarea zonei de director cu prima intrare alocată CP/M Loader-ului (un nume de fișier cu extensia SYS, cum ar fi LK.SYS, ca să păstrăm denumirea originală), și de atașarea zonei de date (de regulă maxim 512 octeți, adică un sector) a CP/M Loader-ului propriu-zis, urmată de restul întregii zone de date a dischetei.
Pentru început m-am ocupat de problema CP/M Loader-ului în sine (zona lui de date). M-am gîndit la 3 opțiuni:
- Din moment ce originalul folosit de acest sistem (ascuns într-un sector alocat D.COM) lucrează cu formatul „protejat” cu sectoare de 4 KB, cea mai simplă opțiune este CP/M Loader-ul de la versiunea de CP/M cu 80 de coloane text vizibile, în forma lui originală care deja lucrează cu același format de pistă cu 9 sectoare.
- Un CP/M Loader adaptat din cel de la versiunea de CP/M cu 80 de coloane text vizibile, dar personalizat cu un logo în stilul celui de la versiunea CP/M CHRIS a lui Pîrvu Cristinel-Dan
- Un CP/M Loader adaptat din originalul folosit de CP/M CoBra (LK.SYS) pentru a funcționa cu piste sistem în format standard. L-am obținut prin aplicarea modificărilor precizate în tabelul aferent, direct asupra fișierului binar original (LK.SYS)
Redau mai jos listingurile în Assembler ale acestor 3 variante.
CP/M Loader pentru CP/M cu 80 de coloane text, CBOT.SYS - codul dezasamblat |
|
CP/M Loader personalizat (cu logo) - listing Assembler | |
|
CP/M Loader versiunea originală CoBra, LK.SYS - codul dezasamblat |
Aceasta e versiunea originală a CP/M Loader-ului folosit cu prima versiune de CP/M CoBra. Este proiectat să funcționeze cu piste sistem formatate cu cîte un singur sector pe o față de pistă, cu 4096 octeți/sector. Imediat după acest listing prezint un tabel cu modificări care adaptează acest CP/M Loader pentru lucrul cu piste sistem cu format standard. Aplicînd aceste modificări direct fișierului binar LK.SYS (cu un editor hexa) se obține foarte elegant un CP/M Loader pentru versiunea CP/M de față (cu 40 coloane text vizibile).
NOTĂ: - Din motive obscure, fișierul LK.SYS păstrat pe dischetele din arhiva mea are dimensiunea de 1152 octeți. Totuși, din listingul dezasamblării de mai jos se vede clar că numai primii 512 octeți conțin codul util de CP/M Loader, restul fiind probabil un rest de cod salvat împreună din greșeală sau poate intenționat pentru a provoca neinițiaților ceva confuzie... Pentru scopul de față am eliminat ce era inutil păstrînd numai primii 512 octeți din acest fișier.
- După cum se vede mai jos, primii 128 octeți par a fi lăsați cu valoarea înscrisă la formatarea dischetei (E5). În mod normal, această zonă liberă ar fi trebuit să înceapă cu o instrucțiune de salt (JP) la începutul zonei de cod propriu-zis (adresa 0080 din acest listing). Din cauză că această instrucțiune de salt lipsește, ce se întîmplă în realitate este că la lansarea CP/M Loader-ului, înainte de execuția codului propriu-zis de CP/M Loader, se execută 128 de instrucțiuni PUSH HL (E5 este codul instrucțiunii PUSH HL) care încarcă zona de stivă cu 256 de octeți în mod inutil. Întrucît la acel moment zona de stivă se află undeva pe la FC00 (la adrese mari) acest lucru nu perturbă îndeplinirea scopului final. Totuși acest ciobănism nu este corect și în versiunea finală de CP/M Loader folosită de mine am adăugat instrucțiunea de salt corespunzătoare la început.
|
|
MODIFICARE LK.SYS PENTRU A FUNCȚIONA CU 9 SECTOARE PE PISTĂ, 512 OCTEȚI/SECTOR |
Locația | Modificare | Comentariu |
$0000 | $E5 -> $00 | primul octet trebuie să fie $00 (NOP), nu $E5 (PUSH HL) |
$0001 | $E5 -> $C3 | opcode pentru JP |
$0002 | $E5 -> $80 | adresa de salt pt. începutul codului (octet inferior) |
$0003 | $E5 -> $00 | adresa de salt pt. începutul codului (octet superior) |
$0097 | $20 -> $24 | $24 x $100 = $2400 = 9 KB = 1 pistă ambele fețe |
$0098 | $FF -> $00 | adresa de start pentru salvare va fi $0000 |
$0099 | $3F -> $00 |
$00AD | $00 -> $01 | comanda MT Read Data să înceapă la sectorul 1 |
$00AE | $00 -> $02 | 512 octeți/sector |
$00AF | $00 -> $09 | EOT, ultimul nr. de sector de pe o pistă |
$00B0 | $00 -> $50 | valoarea GAP3 pentru 9 sect/pistă, 512 octeți/sector |
$01AD | $04 -> $01 | un singur octet de copiat cu LDIR, anume nr. pistă |
$01B1 | $2B -> $06 | înlocuire DEC HL și LD B,(HL) cu LD B,$02 pentru a specifica 512 octeți/sector |
$01B2 | $46 -> $02 |
$01B3 | $2B -> $3E | înlocuire DEC HL și LD A,(HL) cu LD A,$01 pentru a specifica sector nr. 1 |
$01B4 | $7E -> $01 |
$01B5 | $12 -> $00 | ștergere instrucțiune LD (DE),A |
$01C2 | $CB -> $00 | ștergere instrucțiune care schimba opcode pt. INI cu opcode pt. IND |
$01C3 | $DE -> $00 |
$01FC | $23 -> $21 | înlocuire instrucțiuni INC HL și RET cu LD HL,$0000 și RET pentru ca JP (HL) să sară la $0000 după ce ambele piste sistem sînt citite |
$01FD | $C9 -> $00 |
$01FE | $00 -> $00 |
$01FF | $00 -> $C9 |
În afară de codul propriu-zis al CP/M Loader-ului, mai e nevoie de o intrare în director pentru el. Poate fi foarte bine și generat direct și editat cu un editor hexa. Sînt necesari 32 octeți după cum urmează:
octetul 0: | $00 (User Number) |
octeții 1-8: | $4C $4B $20 $20 $20 $20 $20 $20 (Nume fișier, 'LK ') |
octeții 9-11: | $53 $59 $53 (Extensia numelui, 'SYS') |
octetul 12: | $00 (Xl, Nr. extensiei logice de 16KB, octetul inferior al valorii pe 16 biți) |
octetul 13: | $00 (Byte Count, nr. octeți folosiți în ultima înregistrare de 128 octeți. CP/M 2.2 nu suportă acest octet, care este lăsat pe 0) |
octetul 14: | $00 (Xh, Nr. extensiei logice de 16KB, octetul superior al valorii pe 16 biți) |
octetul 15: | $04 (Record Count, nr înregistrări de 128 octeți folosiți de ultima extensie) |
octețul 16: | $02 (octetul inferior al numărului (2) primului bloc alocat pe disc - blocurile 0 și 1 sînt ocupate de directorul CP/M) |
octetul 17: | $00 (octetul superior al numărului primului bloc alocat pe disc) |
octeții 18-31: | $00 (LK.SYS ocupă un singur bloc de 2KB, deci restul de 7 blocuri adresabile de această intrare de director vor avea valoarea $0000) |
În bash o singură linie de comandă rezolvă problema:
echo "00 4C 4B 20 20 20 20 20 20 53 59 53 00 00 00 04 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" | xxd -r -p > CPM_Loader_direntry.bin
Am salvat fișierul astfel generat cu numele CPM_Loader_direntry.bin.
Acesta conține efectiv primii 32 de octeți ai sectorului 1 pista 2 fața 0. Am confecționat astfel prima intrare în director, cu numele LK.SYS (pentru a păstra denumirea standard originală de la CoBra) pe care o voi asocia mai departe cu conținutul CP/M Loader-ului.
Apoi este nevoie de un bloc de octeți $E5 corespunzător restului de sector 1 și sectoarelor 2 3 4 5 6 7, care vor fi goale, adică 512-32 + 6x512 = 3552 octeți. Am generat acest bloc (E5dirfill_1.bin) cu comanda bash:
dd if=/dev/zero bs=1 count=3552 | tr '\000' '\345' > E5dirfill_1.bin
Urmează apoi cei 512 octeți ai CP/M Loader-ului CPM_KRYSS_Loader.bin (sectorul 8 fața 0 pista 2 este primul sector de date de pe disc, deci aici trebuie pus codul Loader-ului).
Apoi urmează alte 10 sectoare goale (sectorul 9 fața 0 pista 2 plus sectoarele 1 2 3 4 5 6 7 8 9 fața 1 pista 2) cu care se completează pista 2.
Și în fine, toate sectoarele (goale) ale pistelor 3-79, adică 77 piste x 20 sectoare.
Deci 10 + 77 x 18 = 1396 sectoare de 512 octeți. Am generat și acest bloc (E5dirfill_2.bin) cu comanda bash:
dd if=/dev/zero bs=512 count=1396 | tr '\000' '\345' > E5dirfill_2.bin
După care am pus cap la cap toate bucățile:
cat CHRSMD_SYS_Tracks_full.bin CPM_Loader_direntry.bin E5dirfill_1.bin CPM_KRYSS_Loader.bin E5dirfill_2.bin > CHRSMD_SYSDISK.img
obținînd o imagine RAW de dischetă sistem CP/M (CHRSMD_SYSDISK.img) cu 40 coloane text vizibile simultan.
Am convertit această imagine în format HFE și punînd-o pe SD Card, am încercat să încarc sistemul de pe unitatea 2.
A urmat o surpriză plăcută: spre deosebire de versiunile de CP/M „SYSGEN 40c” și „SYSGEN 64c” discutate anterior, după ce border-ul a pîlpîit ca de obicei în albastru și mesajul de pornire CP/M a apărut la marginea de sus a ecranului, sistemul s-a lansat fără probleme. La fel de bine a funcționat și cu celelalte 2 opțiuni de CP/M Loader.
De curiozitate m-am apucat să dezasamblez codul CP/M și pentru această versiune cu ștampila „CHRIS” pentru a mă lămuri care e diferența față de primele două versiuni CP/M analizate.
3.1 Identificarea şi dezasamblarea componentelor: CCP, BDOS, BIOS
Punctul de plecare în analiza sistemului de operare CP/M este rutina de încărcare sistem de pe disc aflată în BOOT ROM. Prin analiza ei am aflat cum decurge încărcarea sistemului de pe disc de la zero.
Procesul de încărcare a acestei versiuni de CP/M are următorii paşi:
- La apăsarea tastei D din configuraţia de BOOT, rutina de încărcare CP/M din BOOT ROM caută pe disc un anumit sector care conţine o secvenţă de cod denumită "CP/M Loader". O încarcă în memorie la adresa FC00 şi o lansează în execuţie.
Ca să intru în detalii, algoritmul de localizare a CP/M Loader-ului pe disc este următorul: (se poate afla prin studierea codului dezasamblat de BOOT ROM 80K)
- Se citesc primele 64 înregistrări (intrări, records) din directorul discului în memorie, adică primii 2KB ai directorului;
- În aceste înregistrări se caută primul nume de fișier cu număr utilizator 0 și extensia SYS, unde cei 3 octeți ai extensiei pot avea bitul 7 setat sau nu (0x53, 0x59, 0x53 sau 0xD3, 0xD9, 0xD3). În cazul de față este vorba de fișierul CHRIS.SYS (vezi listingul de mai jos);
- Dacă fișierul este găsit, i se citește prima adresă de bloc alocat pe disc, s-o notăm cu B. CHRIS.SYS are în înregistrarea din director o singură adresă de bloc, care este $0007, deci B=7;
- se calculează o adresă absolută de sector ca fiind Sect# = (B * 8) + 1 = 57;
- Se folosește ca număr de sectoare pe pistă 36;
- Se calculează un număr de pistă ca fiind Trk# = Sect# DIV 36 + 2 = 3 (unde prin DIV am notat operatorul de împărțire cu rezultat întreg);
- Se calculează un număr de sector pe pistă ca fiind Sec# = (Sect# MOD 36 - 1) DIV 4 + 1 = 6;
- Se translatează numărul de sector pe pistă folosind o tabelă de translatare (1 5 9 4 8 3 7 2 6) și se obține Sec# = 3;
- Se citesc de pe disc în memorie la adresa $0000 sectoarele: Sec# (3) de pe pista Trk# (3) fața 0 și Sec#+1 (7, după translatare) de pe pista Trk#+1 (4) fața 0;
- Se face un salt la adresa $0000 lansîndu-se în execuție codul din sectoarele citite, care se așteaptă să conțină codul de CP/M Loader.
Conținutul primelor 8 intrări din director imediat după instalarea sistemului pe o dischetă |
Pornind de la imaginea de disc TEST_CHRSMD.img, primele 8 intrări ale directorului se pot extrage (în Linux) cu comenzile:
head -c 16640 TEST_CHRSMD.img > DATA.bin tail -c 256 DATA.bin > dir_entries_0-7.bin
obținînd astfel fișierul binar dir_entries_0-7.bin cu aceste 8 intrări, listat mai jos în format hexa+ASCII.
Acest listing poate fi generat (în Linux) cu comanda:
hexdump -e '4/1 "%02X "" | " 4/1 "%02X "" | " 4/1 "%02X "" | " 4/1 "%02X "" | " 4/1 "%02X "" | " 4/1 "%02X "" | " 4/1 "%02X "" | " 4/1 "%02X "" | "' -e '32/1 "%_p " "\n"' dir_entries_0-7.bin |
|
Examinînd alocarea spațiului pe disc pentru fișierul D.COM (din intrarea sa în director de mai sus), aflăm că acesta ocupă blocurile 2, 3 și 4. Sectoarele alocate acestor blocuri sînt, respectiv:
Bloc 2: pista 2 fața 0 sector 8, pista 2 fața 1 sectoare 1,3,5
Bloc 3: pista 2 fața 1 sectoare 7,9,2,4
Bloc 4: pista 2 fața 1 sectoare 6,8, pista 3 fața 0 sectoare 1,3
Tot din intrarea în director a fișierului D.COM din listingul de mai sus, Record Count (octetul 15 din intrare, numărul de înregistrări de 128 octeți) este $24 adică 36.
Iar 36 x 128 octeți = 9 sectoare a cîte 512 octeți, deci lungimea fișierului D.COM propriu-zis este de 9 sectoare.
Deci fișierul D.COM are alocate pe disc 3 blocuri (12 sectoare a cîte 512 octeți), dar fișierul D.COM în sine ocupă doar 9 sectoare. Ultimele 3 sectoare, nefiind folosite, pot conține alte date. În cazul de față ultimul sector (sector 3 pista 3 fața 0) este folosit pentru a ascunde codul CP/M Loader-ului, iar cele 2 sectoare dinaintea lui sînt goale (pline cu octetul de formatare $E5). Deci am demonstrat că algoritmul de mai sus va găsi cu succes codul CP/M Loader-ului în sectorul 3 de pe pista 3 fața 0.
- "CP/M Loader"-ul încarcă de pe disc în memorie toate sectoarele pistelor sistem, în ordinea lor fizică (pista 0 faţa 0, pista 0 faţa 1, pista 1 faţa 0, pista 1 faţa 1). Întrucît (după cum am menționat mai sus) pentru această versiune de CP/M pistele sistem conțin codul sistemului scris în oglindă (de la coadă la cap), sectoarele citite în ordine normală de pe disc sînt salvate în memorie începînd cu adresa 3FFF, în ordine descrescătoare a adreselor, pînă la 0000. După ce toate sectoarele au fost încărcate, se face un salt la adresa 0000, adică se lanseaza în execuţie codul conţinut în ceea ce ar fi trebuit să fie primul sector de pe disc (pista 0 faţa 0 sector 1). Acesta se numeşte "Boot Sector".
- Codul conţinut în "Boot Sector" mută componentele sistemului (CCP, BDOS, BIOS) la adresele la care ele trebuie rulate în mod normal şi apoi face un salt la rutina BOOT din BIOS (la adresa $F600), iniţializînd astfel sistemul.
Codul CP/M Loader extras din sectorul 3 pista 3 fața 0 - codul dezasamblat
Se observă rutina de afișare a logo-ului „PLUS”, inserată în prima parte a CP/M Loader-ului care de obicei este goală. |
|
Boot Sector extras din pistele sistem - codul dezasamblat |
NOTĂ:
Întrucît m-am așteptat ca Boot Sector-ul să conțină cod util în toți cei 512 octeți ai unui sector standard, am extras primii 512 octeți din blocul de cod al pistelor sistem (după ce acest bloc a fost în prealabil întors pe dos pentru a-l aduce la ordinea normală, pistele sistem fiind scrise de-a-ndoaselea, după cum am mai menționat). Dar din listingul de mai jos se vede că zona utilă de cod de Boot Sector este destul de mică și este în întregime conținută în primii 256 octeți. De la adresa 0100 deja începe codul modulului BIOS, care după cum se vede este relocat începînd la $6200. Deci practic Boot Sector-ul are mărimea de 256 de octeți. |
|
După cum se vede din listingul alătural, codul din Boot Sector, responsabil pentru plasarea efectivă a sistemului în memorie la adresele corespunzătoare, sparge blocul de cod sistem încărcat din pistele 0 și 1 în 6 bucăți separate și execută următoarele lucruri:
- Dezactivează întreruperile și fixează stiva la adresa $0100
- Programează modul de funcționare a interfeței 8255 în mod compatibil Spectrum
- Setează culoarea BORDER albastru și pune semnalul 06 de la portul C al 8255 pe „1” logic pentru a permite accesul CPU la bancul RAM video
- Relochează modulul BIOS în zona $F600-$FFFF;
- Relochează modulul BDOS în zona $E800-$F5FF (imediat sub BIOS);
- Relochează modulul CCP în zona $7800-$7FFF (zona superioară a RAM video). Această zonă este utilizată ca o copie de rezervă a CCP. La lansarea efectivă a sistemului, după terminarea acestui cod de Boot Sector, modulul CCP va mai fi copiat încă o dată în poziția lui finală, adică $E000-$E7FF, imediat sub BDOS. Iar la nevoie, dacă vreun program CP/M tranzitoriu suprascrie zona CCP $E000-$E7FF, modulul CCP va fi recopiat direct din zona copiei de rezervă fără a mai fi nevoie să se acceseze discul. O alternativă elegantă la procedura standard CP/M de readucere a modulului CCP în memorie de pe disc;
- Relochează un bloc de date (generatorul de caractere plus rutine auxiliare BIOS) pe care l-am denumit BLOCK#1 în zona $6200-$7FFF (jumătatea superioară a RAM video)
- Readuce semnalul 06 de la portul C al 8255 pe „1” logic pentru a schimba accesul CPU de la bancul RAM video la bancul DRAM#1
- Relochează zona de memorie $3700-$3FFF conținînd codul de help și demo în zona $0100-$09FF care este adresa de lansare a executabilelor CP/M
- Șterge zona de memorie $0B00-$E805 prin umplere cu octetul 1A
- Execută un salt la adresa $F600, care este adresa de start a funcției BIOS cu numărul 0, BOOT (Cold Boot).
|
Deci în acest punct am determinat toate blocurile componente ale sistemului CP/M, care se pot extrage separat și dezasambla pentru studiu amănunțit.
3.2 Investigarea algoritmului ascuns de protecție a formatului pistelor sistem
În continuare facem o comparație între codul sistemului de față și codul primelor două versiuni CP/M analizate mai înainte („SYSGEN 40c” și „SYSGEN 64c”) pentru a vedea de ce versiunea de față nu pare să mai aibă încorporat algoritmul de protecție din primele două. Vom porni la fel, de la punctul de start determinat din studiul Boot Sector-ului. Acolo, după cum am văzut, după relocarea blocurilor de date/cod se face un salt la adresa $F600, care este adresa de start a modulului BIOS, mai precis a funcției numărul 0, BOOT (Cold Start).
Modulul BIOS - codul dezasamblat |
|
După cum se observă din listingul BIOS, la adresa $F600 se află o instrucțiune de salt la adresa $FE2D. Acolo găsim același lucru ca la primele 2 versiuni CP/M analizate: o rutină care mai întîi programează modul de funcționare a Z80-CTC, apoi încarcă regiștrii BC cu $7230, DE cu $F873 și HL cu $F601. Mai departe vedem cum folosind registrul HL ca pointer, se modifică două locații de memorie consecutive, $F601-$F602, care conțin însăși adresa funcției BIOS BOOT pe care o examinăm, cu numărul 0. În loc de $FE2D, acolo se depune valoarea $F873. La adresa $F873 se află o scurtă rutină care provoacă revenirea în codul de BOOT ROM, deci aceeași manifestare „sabotoare” ca la primele 2 versiuni CP/M.
Cu alte cuvinte, funcția BIOS BOOT își schimbă singură adresa de start după ce este executată prima dată. Pînă aici nici o diferență. Să vedem ce descoperim mai departe...
În continuare, aceleași șmecherii „deștepte” menite să ascundă modul de execuție: este salvată pe stivă valoarea $F603, urmată de valoarea $7230. După care se setează semnalul 06 pe 1 logic, pentru acces CPU la VRAM și se execută un salt la adresa $7071, care se află în bucata de cod denumită BLOCK#1. Și pînă aici, încă nici o diferență.
Listingul dezasamblării acestui bloc de cod este prezentat mai jos. La adresa $7071 se află o secvență de cod care testează ce unitate fizică este READY și o atribuie ca unitate logică A:, după care execută un RET. Acest RET provoacă un salt la adresa $7230 care după cum am menționat mai sus a fost ultima valoare salvată pe stivă, iar la adresa $7071 s-a ajuns cu o instrucțiune JP și nu CALL. Și pînă aici, încă nici o diferență.
Zona de cod BLOCK#1 - codul dezasamblat |
|
Deci în continuare ne ducem la adresa $7230 (tot în cadrul BLOCK#1), unde, foarte important, găsim o instrucțiune EXX neurmată de o altă pereche, lucru important de reținut. Apoi se încarcă HL cu adresa de start a mesajului de pornire CP/M și se apelează o rutină care afișează acest mesaj. Următoarea instrucțiune, JP (HL) va executa un salt la adresa conținută în HL după terminarea rutinei de afișare a mesajului CP/M, iar HL, fiind folosit pe post de pointer în timpul afișării mesajului, va conține adresa imediat următoare zonei de memorie care conține mesajul. Dacă ne uităm în BIOS, după mesajul stocat la $FE00-$FE28 se află un octet $00 la adresa $FE29, urmat de instrucțiunea JP $709A. Deci în continuare ne ducem la adresa $709A (tot în cadrul BLOCK#1). Și pînă aici, încă nici o diferență.
La adresa $709A găsim altă secvență de cod care înlocuiește caracterul '0' din mesajul "Insert disk E in drive 0" cu numărul unității fizice găsite anterior ca READY, după care o mică mișcare de octeți dependentă de tipul unității detectate (DD sau SD):
Dacă unitatea fizică găsită READY este 0 sau 1 (Single Density), la adresele $F659 și $FB4A este depus cuvîntul $F6EA, după care în registrul DE este încărcat cuvîntul aflat la ($F6E8-$F6E9).
Dacă unitatea fizică găsită READY este 2 sau 3 (Double Density), la adresele $F659 și $FB4A este depus cuvîntul $F6CA, după care în registrul DE este încărcat cuvîntul aflat la ($F6C8-$F6C9).
Din listingul BIOS aflăm că valoarea pe 2 octeți stocată la ($F6C8-$F6C9), pentru unitățile 2 sau 3 este $F6D9. Deci acum DE=$F6D9. Și pînă aici, încă nici o diferență.
Iar în continuare ajungem la secvența de cod relevantă: se trimite o comandă Read ID la unitatea găsită în prealabil ca READY, pe pista 0 fața 0, Această comandă va citi datele de identificare ale unicului sector de pe acea față de pistă. Relevante în acest caz sînt (vezi documentația 8272) R (numărul de sector) și N (octet indicînd mărimea sectorului).
Mai departe în registrul BC se încarcă valoarea compusă RN obținută cu Read ID și se salvează pe stivă. Apoi urmează o instrucțiune EXX care inversează efectul celei menționate anterior și valoarea RN salvată pe stivă se readuce, de data aceasta în registrul HL. După care se scade valoarea în DE din valoarea în HL. Cu alte cuvinte se compară valoarea RN (obținută cu Read ID pentru sectorul de pe pista 0 fața 0) cu valoarea conținută în DE înainte de prima instrucțiune EXX de la adresa $7230.
Și aici se vede diferența față de codul primelor două versiuni CP/M:
la adresa $70CE, în loc de RET Z (care condiționa continuarea normală a execuției, cu funcția BIOS WBOOT, de identitatea valorilor comparate) acum se află un simplu RET, adică exact soluția adoptată de mine pentru dezactivarea protecției la primele două versiuni CP/M.
Deci se pare că Pîrvu Cristinel-Dan aka CHRIS a dezactivat intenționat această rutină pe care la rîndul lui a identificat-o în codul sistemului. La asta chiar nu m-aș fi așteptat. Nu știu ce motive a avut să facă asta, practic a înlesnit munca unui eventual hăcuitor de coduri (ca mine de exemplu).
În concluzie, algoritmul de protecție ascuns în BIOS este practic dezactivat și la această versiune. Din cele 3 modificări propuse de mine pentru primele două versiuni:
- La adresa $70CE din zona BLOCK#3 încărcat în memorie, instrucțiunea RET Z trebuie înlocuită cu RET (necondițional, pentru a elimina necesitatea succesului comparației);
- La adresa $FE50 din modulul BIOS încărcat în memorie, instrucțiunea pe un singur octet LD (HL),E trebuie eliminată (prin simplă înlocuire cu un NOP) pentru a lăsa adresa funcției BIOS BOOT nemodificată;
- La adresa $FE52 din modulul BIOS încărcat în memorie, instrucțiunea pe un singur octet LD (HL),D trebuie eliminată (prin simplă înlocuire cu un NOP) pentru a lăsa adresa funcției BIOS BOOT nemodificată.
numai ultimele două mai rămîn de pus în aplicare, pentru a lăsa nemodificată adresa funcției BIOS BOOT.
Mai pe scurt, modificările sînt următoarele:
MODIFICARE COD BIOS CP/M PENTRU ELIMINAREA PROTECȚIEI DE FORMAT PISTĂ SISTEM |
Adresa în CP/M rezident |
Adresa în MODUL.bin |
Adresa în CHRSMD_SYS_Tracks_full.bin |
Modificare |
$70CE
$FE50
$FE52 |
$02CE în BLOCK#1.bin
$0850 în BIOS.bin
$0852 în BIOS.bin |
$0DCE
$0950
$0952 |
$C8 -> $C9 (deja schimbat) $73 -> $00
$72 -> $00 |
Așa că în final am modificat ultimele 2 locații direct în fișierul CHRSMD_SYS_Tracks_full.bin, conținînd imaginea completă a celor două piste sistem, pe care am salvat-o ca CHRSMD_SYS_Tracks_full_modified.bin.
3.3 Generarea imaginii RAW de dischetă sistem cu pistele 0 și 1 în format standard
Acum că blocul cu codul sistemului propriu-zis este „domesticit”, putem genera o imagine completă, funcțională, de dischetă sistem cu același format pe toate pistele, de la 0 la 79. Revenind la cele 3 opțiuni de CP/M Loader menționate anterior:
1. Pentru o imagine RAW de dischetă sistem CP/M (CHRSMD_KR_SYSDISK.img) cu CP/M Loader-ul original al acestei versiuni, am folosit comanda:
cat CHRSMD_SYS_Tracks_full_modified.bin CPM_Loader_direntry.bin E5dirfill_1.bin CPM_KRYSS_Loader.bin E5dirfill_2.bin > CHRSMD_KR_SYSDISK.img
2. Pentru o imagine RAW de dischetă sistem CP/M (CHRSMD_CL_SYSDISK.img) cu CP/M Loader personalizat (logo), am folosit comanda:
cat CHRSMD_SYS_Tracks_full_modified.bin CPM_Loader_direntry.bin E5dirfill_1.bin CPM_CHRSMD_Loader.bin E5dirfill_2.bin > CHRSMD_CL_SYSDISK.img
3. Pentru o imagine RAW de dischetă sistem CP/M (CHRSMD_LK_SYSDISK.img) cu CP/M Loader adaptat de la versiunea originală a CP/M CoBra (1989 ITCI Brasov), am folosit comanda:
cat CHRSMD_SYS_Tracks_full_modified.bin CPM_Loader_direntry.bin E5dirfill_1.bin LK.SYS_modified.bin E5dirfill_2.bin > CHRSMD_LK_SYSDISK.img
3.4 Modificarea sistemului pentru a fi lansat și de pe unitățile fizice 0 și 1 ca unități DD
Toate sistemele CP/M scrise pentru CoBra au fost concepute pe vremea cînd încă mai existau și unități floppy simplă densitate (de regulă de 8"). Ca urmare în codul sistemului a fost rezervată folosirea unităților fizice 0 și 1 pentru unități simplă densitate (SD), și folosirea unităților fizice 2 și 3 pentru unități dublă densitate (DD).
Consecința este că, dacă folosim unități floppy dublă densitate (singurele care mai există în practică), nu putem încărca CP/M de pe ele decît dacă le conectăm ca unitate fizică 2 sau 3. Redau mai jos un snapshot care arată ce se întîmplă atunci cînd încerc să încarc sistemul de pe discheta introdusă în unitatea fizică 0:
|
Ca un mic comentariu, am să menționez și de unde anume vine eroarea din imaginea alăturată:
Cam toate versiunile CP/M pentru CoBra au încorporată în BIOS o rutină care, imediat după lansarea sistemului și afișarea mesajului de început la marginea de sus a ecranului, citește pista 0 fața 0 a discului unde se așteaptă să găsească un sector unic de 4096 octeți, cu numărul de sector 32. După aceea compară mărimea sectorului găsit și numărul lui cu niște valori stocate într-o zonă de date a CP/M. Dacă valorile citite nu corespund cu valorile stocate, se ia decizia de a reseta calculatorul înapoi în configurația de BOOT, lansînd codul din BOOT ROM. Scopul acestei șmecherii este de a împiedica o eventuală hăcuială din partea neaveniților cu scopul de a copia codul sistemului și a-l transpune în piste sistem cu formatare normală.
Interesant este că unele versiuni CP/M au această decizie dezactivată (nu e cazul versiunii de față), dar testul în sine tot este executat. Testul constă efectiv dintr-o instrucțiune Read ID trimisă la 8272, urmată de citirea de la 8272 a octeților cu rezultatul comenzii. Mesajul de eroare din imaginea alăturată exact asta exprimă: eroare la execuția comenzii Read ID, din cauza faptului că densitatea discului (DD) nu corespunde cu densitatea specificată în tabelul DPB atribuit unității respective (SD). Ca urmare citirea datelor de pe disc nu se poate efectua.
|
Întrucît limitarea mi se pare aiurea, am decis să modific codul sistemului pentru a-l convinge să folosească toate cele 4 unități fizice posibile ca unități dublă densitate. Pentru a realiza asta, este nevoie de ceva glagorie în legătură cu funcționarea internă a CP/M. Mai exact și mai pe scurt, pentru fiecare unitate fizică există un Disk Parameter Block (DPB, 15 octeți) și un Disk Parameter Header (DPH, 16 octeți). DPB conține parametrii caracteristici ai unității cum ar fi numărul de sectoare pe pistă și mărimea blocului de alocare pe disc. DPH conține adresa de început a DPB, adresa de început a tabelei de translatare sectoare (XLT), și alte cîteva adrese ale unor zone de memorie folosite de CP/M pentru fiecare unitate fizică în parte.
O teorie ceva mai superficială ar fi că modificarea DPH pentru unitățile 0 și 1 astfel încît să folosească aceleași XLT și DPB ca unitățile 2 și 3 ar trebui să rezolve problema. În practică, pe lîngă asta mai e nevoie și de ceva hăcuială suplimentară a codului din BIOS, pentru a vedea dacă nu cumva mai există și vreo rutină care să manevreze acești parametri în funcție de numărul unității fizice.
Și într-adevăr am găsit și astfel de rutine:
Funcția BIOS SECTRAN (vezi listingul BIOS de mai jos) folosește o discriminare între unitățile 0,1 și 2,3 în calcularea numărului sectorului logic. Pentru scopul de față, ar fi nevoie de ștergerea instrucțiunii JR Z,$F812 de la adresa $F80C prin înlocuire cu două NOP-uri.
Modulul BIOS din CP/M CHRSMD - listingul dezasamblării |
|
Blocul de cod denumit de mine BLOCK#3 (vezi listingul BLOCK#3 de mai jos și de asemenea listingul Boot Sector-ului dezasamblat de și mai jos) folosește două astfel de condiționări în rutinele BIOS READ/WRITE, la adresele $6EA9 (JR Z,$6EAD) și $6EC3 (JR NZ,$6ED4). Pentru scopul de față ar fi nevoie de înlocuirea JR Z,$6EAD cu două NOP-uri și a instrucțiunii JR NZ,$6ED4 cu JR $6ED4.
Modulul BLOCK#3 din CP/M CHRSMD - listingul dezasamblării |
|
Tot în BLOCK#1 mai este o condiționare la adresa $7035 (JR Z,$703F) și încă una la adresa $70AC (JR Z,$70B3), care ambele ar trebui înlocuite și ele cu cîte două NOP-uri,
Sintetizînd toate acestea, am scris un Boot Sector modificat (cu lungimea totală de 256 octeți) care să modifice aceste locații de memorie imediat după despachetarea blocurilor de cod CP/M la adresele lor de lucru și înainte de lansarea efectivă a sistemului. Prezint mai jos listingul Boot Sector-ului original și al acestui Boot Sector modificat.
Boot Sector-ul original din CP/M CHRSMD - listing dezasamblare | Boot Sector-ul modificat pentru CP/M CHRSMD - listing în Assembler |
|
|
Am înlocuit primii 256 octeți (codul sistemului începe după primii 256 octeți, după cum se poate vedea din Boot Sector-ul original) din blocul de cod conținînd pistele sistem cu acest Boot Sector modificat:
tail -c 18176 "CHRSMD_SYS_Tracks_full_modified.bin" > DATA.bin
cat Boot_Sector_modified.bin DATA.bin > CHRSMD_SYS_Tracks_full_modified_1.bin
rm -fv DATA.bin
și am generat din nou cele trei versiuni de dischetă sistem discutate anterior:
1. Pentru o imagine RAW de dischetă sistem CP/M (CHRSMD_KR_SYSDISK.img) cu CP/M Loader-ul modificat al versiunii CP/M cu 80 coloane text, am executat comanda:
cat CHRSMD_SYS_Tracks_full_modified_1.bin CPM_Loader_direntry.bin E5dirfill_1.bin CPM_KRYSS_Modified_Loader.bin E5dirfill_2.bin > CHRSMD_KR_SYSDISK.img
2. Pentru o imagine RAW de dischetă sistem CP/M (CHRSMD_LK_SYSDISK.img) cu CP/M Loader adaptat de la versiunea originală a CP/M CoBra (1989 ITCI Brasov), am executat comanda:
cat CHRSMD_SYS_Tracks_full_modified_1.bin CPM_Loader_direntry.bin E5dirfill_1.bin LK.SYS_modified.bin E5dirfill_2.bin > CHRSMD_LK_SYSDISK.img
3. Pentru o imagine RAW de dischetă sistem CP/M (CHRSMD_CL_SYSDISK.img) cu CP/M Loader personalizat (logo), am executat comanda:
cat CHRSMD_SYS_Tracks_full_modified_1.bin CPM_Loader_direntry.bin E5dirfill_1.bin CPM_CHRSMD_Loader.bin E5dirfill_2.bin > CHRSMD_CL_SYSDISK.img
Și am obținut astfel trei variante de dischetă sistem de pe care CP/M poate fi încărcat cu succes folosind oricare din unitățile 0-3.
În final, link-uri pentru download ale celor trei tipuri de imagini de dischetă sistem menționate mai sus, în format RAW precum și în format HFE utilizabil cu emulatorul floppy HxC:
PRECIZARE IMPORTANTĂ:
Pentru lansarea sistemului CP/M de pe imaginile de dischetă de mai jos NU ESTE NECESAR „Boot-ul CoBra Unificat” scris de mine și prezentat aici la secțiunea „Software / BOOT ROM”.
Aceste imagini de dischetă pot fi foarte bine lansate cu un cod de BOOT original de 2KB (ca pe vremuri). Dealtfel, și dacă se folosește „Boot-ul CoBra Unificat”, pentru lansarea oricărui sistem de pe dischetă, din meniul lui va trebui intrat mai întîi într-unul din codurile BOOT vechi, și apoi din acel BOOT, cu tasta D se încarcă sistem CP/M de pe dischetă. |
IMAGINI DISCHETE SISTEM BOOTABILE - CP/M CHRIS MDSP (cu piste sistem formatate normal, 9 sectoare/pistă) cu sistem încărcabil numai de pe unitățile fizice 2 și 3 |
IMAGINI DISCHETE SISTEM BOOTABILE - CP/M CHRIS MDSP (cu piste sistem formatate normal, 9 sectoare/pistă) cu sistem încărcabil de pe orice unitate fizică (0, 1, 2 sau 3) |