\ ***************************************************** \ * * \ * BOOTMA-3.FTH * \ * * \ * Zutaten fuer FAT-Reparatur und Bootmanager unter * \ * Turbo-FORTH-83 und ZF * \ * * \ * Fred Behringer - Forth-Gesellschaft - 01.03.2009 * \ * * \ ***************************************************** \ Fuer Turbo-Forth: \ FORTH INCLUDE BOOTMA-1.FTH INCLUDE BOOTMA-2.FTH INCLUDE BOOTMA-3.FTH [ret] \ Hierbei ist die erste Include-Datei die aus Heft 3/2008, die zweite die aus \ Heft 4/2008 und die dritte die hier zu besprechende. Man beachte, dass man \ hier - bis zum allerletzten INCLUDE (man darf natuerlich auch alles \ kleinschreiben) - alle Eingaben 'schachteln' kann, ohne zwischendurch per \ [ret] 'absetzen' zu muessen. \ Fuer ZF: \ ZF [ret] [ret] \ FLOAD BOOTMA-1.FTH [ret] FLOAD BOOTMA-2.FTH [ret] FLOAD BOOTMA-3.FTH [ret]. \ In BOOTMA-1.FTH muss vorher attributs off per '\' auskommentiert werden (in ZF \ gibt es kein attributs). Bei ZF, zumindest in der erweiterten Fassung der \ Lokalen Forth-Gruppe Moers, geht die Schachtelung der Kommandozeilen-Eingaben \ nicht, ohne dass man zwischendurch per [ret] absetzt. Die Eingabe von ZF muss \ sogar zweimal quittiert werden. \ Glossar \ chs>lba ( c h/c h s/h sp -- sl ) \ aus CHS-Sektor-Angaben LBA-Wert ermitteln \ lba>chs ( sl h/c s/h -- c h sp ) \ aus LBA-Sektor-Wert CHS-Angaben ermitteln \ getLBAsect ( sl -- fl ) \ LBA-Sektor in den Puffer sectbuf holen \ putLBAsect ( sl -- fl ) \ LBA-Sektor aus sectbuf zurueckschreiben \ sect>bak ( -- ) \ sectbuf in Datei SECT.BAK schreiben \ bak>sect ( -- ) \ Sektor aus SECT.BAK nach sectbuf holen \ mbr>bak ( -- ) \ sect>bak fuer mbr, Holen schon von HD \ bak>mbr ( -- ) \ bak>sect fuer mbr, Schreiben nach HD \ Will man sect>bak zum Sichern eines Sektors (z.B. des MBRs) verwenden, und \ bak>sect zum Rueckschreiben nach sectbuf, dann muss man den Vorgang noch durch \ getLBAsect oder putLBAsect (bei LBA-Betrachtungsweise) oder (getsect) oder \ (putsect) (bei CHS-Betrachtungsweise) vervollstaendigen, damit er auf die \ Festplatte einwirkt. \ c : Zylindernummer \ h : Kopfnummer \ sp : Sektornummer (phys., also nach CHS) \ sl : Sektornummer (log., also nach LBA) \ h/c : Koepfe pro Zylinder \ s/h : Sektoren pro Kopf \ Die Nummer des zugeordneten logischen Sektors berechnet sich (vergleiche \ Artikeltext) zu: \ sl = ((c * h/c + h) * s/h) + sp - 1 \ Das Forth-Wort chs>lba liefert aus den CHS-Eingaben heraus den logischen \ Sektor sl. Der Einsatz von chs>lba hat natuerlich nur dann einen Sinn, wenn \ das BIOS der Anlage den erweiterten Interrupt 13h (insb. den Platten-Zugriff \ auf mehr als 8 GB) ueberhaupt verarbeiten kann. \ Es ist nicht gerade leicht, solche Aufgaben wie die vorliegende mit einem \ 16-Bit-Forth-System bewaeltigen zu wollen. Ich mache daher die folgenden \ Einschraenkungen: Eingaben seien einfachgenau. Nicht unueblich sind \ h/c = 255 und s/h = 63. Wenn man beides malnimmt (vergleiche Formel), bekommt \ man wieder eine Zahl, die mit 16 Bit auskommt. Koepfe und Sektoren bereiten \ also keine Schwierigkeiten. Bei c (Zylinder) macht sich die Beschraenkung (bei \ den heutigen Festplattenkapazitaeten) am staerksten bemerkbar: Uebergrosse \ Platten sind nicht mehr so einfach behandelbar. Man koennte das Forth-Wort \ chs>lba als Code-Definition fassen, um der 16-Bit-Beschraenkung elegant zu \ entfliehen. Ich will darauf verzichten, da ja die angestrebte Umrechnung \ sowieso nur dann sinnvoll ist, wenn die Bestandteile der CHS-Adressierung, \ C-H-S, in Groessenordnungen liegen, die ohnehin ins Schema passen. : chs>lba ( c h/c h s/h sp -- sl ) \ sl doppeltgenau, alle anderen einfach dup 1 < >r 2dup < r> \ Nicht 1 <= sp <= s/h ? or abort" Fehler!" \ Ja, dann Fehler und Abbruch. 1 - s>d >r >r \ Sonst: Sektoranteil doppeltgenau nach r: over ff00 and \ Oberes Nibble <> 0 ? abort" Fehler!" \ Ja, dann Fehler und Abbruch. tuck um* >r >r \ Sonst: h*s/h doppeltgenau nach r: * um* \ s/h * h/c * c r> r> r> r> d+ d+ ; \ Das folgende Forth-Wort lba>chs liefert bei gegebenem LBA-Sektor die \ zugehoerigen CHS-Werte. Die Hoechstzahl an Koepfen (pro Zylinder) und die \ Hoechstzahl an Sektoren (pro Kopf) muessen ebenfalls bekannt sein. Leitbild \ ist die folgende Formel (siehe Artikeltext): \ (sl+1)/(h/c * s/h) = c + Rest1 \ Rest1 /( s/h) = h + Rest2 \ Rest2 = sp : lba>chs ( sl h/c s/h -- c h sp ) \ sl doppeltgenau, alle anderen einfach >r r@ * >r \ erst s/h nach r: , dann h/c * s/h nach r: 1. d+ \ ab jetzt Sektorzaehlung beginnend bei 1 r> um/mod \ (sl+1)/(h/c * s/h) verarbeiten swap r> \ c Rest1 s/h /mod swap ; \ c h Rest2 (= c h sp) \ Ich verzichte hier auf die Absicherung gegen Eingabefehler bei h/c und s/h. \ Die Technik dazu kann aus dem Forth-Wort chs>lba uebernommen werden. \ Was im vorliegenden Artikel eigentlich interessiert, ist die Entwicklung eines \ Forth-Wortes, mit dessen Hilfe man sich Sektoren auch von groesseren \ Festplatten ansehen kann, Sektoren also, bei denen die CHS-Adressierung \ versagt und bei denen man auf LBA-Adressierung zurueckgreifen muss. Dass man \ solche Sektoren dann auch wieder 'zurueckschreiben' kann, versteht sich von \ selbst. Zunaechst benoetige ich einen Puffer zur Parameter-Uebergabe: 28 allot here \ Platz fuer mindestens 18h Bytes here 0f and - \ dap (disk address packet) an den Anfang des Paragraphen 18 - \ Anfangsadresse des dap constant dap \ Liefert bei Aufruf Anfang des dap-Puffers code csegment ( -- cseg ) cs push next end-code \ In Turbo-Forth gibt es die Konstante dsegment. Da man bei Definition des \ Sektorpuffers sectbuf die Segmente ds und cs als wertegleich behandeln kann, \ haette im gleich folgenden Forth-Wort getLBAsect der Aufruf der Konstanten \ dsegment gereicht, um fuer das dap an das sectbuf-Segment zu gelangen. In ZF \ gibt es aber keine Konstante dsegment und es war mir zu umstaendlich \ herauszusuchen, was man in solchen Faellen in ZF zu machen hat. Daher der \ Zusatzaufwand mit der Pseudokonstanten csegment. (In ZF ausprobiert! Geht \ prima!) \ LBA-Sektor sl (32 Bit) vom Boot-Laufwerk (hier immer 80) nach Puffer sectbuf \ holen. fl <> 0 bedeutet Fehler (den ich hier nicht naeher spezifizieren \ moechte). Ein paar leicht einfuegbare Zusatzzeilen wuerden es auch gestatten, \ gleich mehrere Sektoren in einem Zug einzulesen (oder abzuspeichern). In \ voller Allgemeinheit waere das unnoetig schwierig. Eine brauchbare \ Organisation des dann noetig werdenden groesseren Transfer-Puffers wuerde \ zusaetzliche Ueberlegungen kosten. Ich verzichte hier darauf. Theoretisch \ koennte ich ja mit drei Forth-Zeilen eine Schleife auch schon mit der \ Code-Definition getLBAsect aufbauen. Dass mit solch groben Mitteln dann jedoch \ beispielsweise das Klonen einer ganzen Festplatte unertraeglich lange dauern \ wuerde, ist leicht vorauszusehen. code getLBAsect ( sl -- fl ) \ sl doppeltgenau; Fehler, wenn fl <> 0 18 # dap #) mov \ Laenge des dap nach dap 1 # dap 02 + #) mov \ Sektoranzahl (1) nach dap sectbuf # dap 04 + #) mov \ Adresse sectbuf nach dap csegment # dap 06 + #) mov \ Segment sectbuf nach dap 80 # dl mov \ Boot-Laufwerk nach dl ax pop \ sl hi ax dap 0a + #) mov \ nach dap ax pop \ sl lo ax dap 08 + #) mov \ nach dap 0 # dap 0c + #) mov \ uebergrosse Anteile auf 0 legen 0 # dap 0e + #) mov \ dito si push \ si (=ip) sichern dap # si mov \ ds:si mit ds:dap laden 4200 # ax mov \ Funktion 'erweitertes Lesen' (LBA) 13 int \ Interrupt aufrufen si pop \ si (=ip) wiederherstellen ah al mov \ Jetzt ax = 0 bei Erfolg ax push \ ax <> 0 => Fehler next end-code \ Achtung: Wenn man mit getLBAsect beim ersten Versuch Schiffbruch erleiden \ sollte, dann ist es so gut wie sicher, dass man sl einfachgenau statt \ doppeltgenau angesetzt hat! sl muss als 'Punktzahl' eingegeben werden! \ In Offset 'dap 04 +' von dap wird die 'Lang-'Adresse des Uebertragungspuffers \ in der Form segment:sectbuf (bei mir beispielsweise 2860:6750) festgehalten, \ und zwar in der Little-Endian-Form 50 67 60 28 . Steht hier der Wert \ ffff:ffff, dann soll laut 'Browns Liste' (ich hatte keine Veranlassung, das \ nachzupruefen) ab Offset 'dap 10 +' die lineare 64-Bit-Adresse des \ Uebertragungspuffers abgelegt werden. \ Und hier das Schreib-Pendant zu getLBAsect. \ Man huete sich vor unueberlegtem Aufruf! code putLBAsect ( sl -- fl ) \ sl doppeltgenau; Fehler, wenn fl <> 0 18 # dap #) mov \ Laenge des dap nach dap 1 # dap 02 + #) mov \ Sektoranzahl (1) nach dap sectbuf # dap 04 + #) mov \ Adresse sectbuf nach dap csegment # dap 06 + #) mov \ Segment sectbuf nach dap 80 # dl mov \ Boot-Laufwerk nach dl ax pop \ sl hi ax dap 0a + #) mov \ nach dap ax pop \ sl lo ax dap 08 + #) mov \ nach dap 0 # dap 0c + #) mov \ uebergrosse Anteile auf 0 legen 0 # dap 0e + #) mov \ dito si push \ si (=ip) sichern dap # si mov \ ds:si mit ds:dap laden 4300 # ax mov \ Funktion 'erweitertes Schreiben' (LBA) 13 int \ Interrupt aufrufen si pop \ si (=ip) wiederherstellen ah al mov \ Jetzt ax = 0 bei Erfolg ax push \ ax <> 0 => Fehler next end-code \ Und wozu das Ganze? \ ------------------- \ Wenn man nach vielem Experimentieren endlich solche Forth-Worte wie die \ obigen vier 'auf die Beine gestellt' hat, haelt man die Frage nach \ Anwendungsmoeglichkeiten fuer selbstverstaendlich und daher fuer \ ueberfluessig. Man kann es nicht verstehen, wenn man trotzdem gefragt wird: \ "Und warum hast du dich jetzt damit beschaeftigt? Was kann man denn damit \ anfangen?" - Man will ueberhaupt gar nichts damit anfangen! Man macht es, weil \ man es (endlich) verstanden hat und es jetzt kann. Was treibt denn ein Kind \ dazu, eine Sandburg zu bauen? Und einen Maler, ein Bild zu malen? Und einen \ Wissenschaftler, zwanzig Jahre seines Lebens in die Suche nach einer Loesung \ eines (meist sogar selbsterfundenen) Problems zu stecken? \ Na ja, ich darf beispielsweise auf das grosse Problem der Datensicherung, des \ 'Backups', hinweisen: Es gibt Betriebssysteme, die bei ihrer Installation den \ Master-Boot-Record (den MBR) eines anderen, schon installierten \ Betriebssystems ueberschreiben. Es gibt 'Anwender', die sich dann nicht mehr \ zu helfen wissen. Wie leicht ist es aber, wenn man sich fuer solche Faelle ein \ Forth-Tool-Belt geschmiedet hat, mit dessen Hilfe MBRs in eine Datei \ geschrieben (gesichert) und von dort auch wieder zurueckgeholt werden koennen. \ Dazu braucht man wirklich kein 'Paragon' oder 'Acronis' oder sonstwie \ geartetes 180-MB-Paket zu kaufen - und auch noch zwangsregistrieren zu lassen! \ Unter Linux laesst sich der MBR mit einer einzigen dd-Zeile in eine Datei \ kopieren. Mit DEBUG aus DOS geht es nicht, jedenfalls nicht 'auf Anhieb'! \ (Man mache sich klar, warum nicht.) Aber mit Forth! In Forth geht alles! Und \ man kann es sich genau so einrichten, wie man es in seiner \ hoechstpersoenlichen Programmierumgebung gerade braucht. Selbstverstaendlich \ kann man mit wenigen Forth-Handgriffen (siehe Teil 1 und 2 der vorliegenden \ Serie) auch die 'Bootsektoren' der einzelnen Partitionen und sogar die \ 'Partitionstabellen' der logischen Laufwerke der erweiterten Partition in \ Dateien sichern. \ Ein weiteres Vorhaben waere der Umzug der zu eng gewordenen Festplatte auf \ eine inzwischen preisguenstigere groessere Platte, das 'Klonen'. Was fuer ein \ Aufwand, wenn man herausbekommen will, welches kostenlose Klon-Programm \ unter welchen Bedingungen funktioniert! Geht das eine fuer XP konzipierte auch \ unter ME? Und so weiter. Mit den in der vorliegenden Serie entwickelten \ wenigen Forth-Worten koennte man ein solches Klon-Vorhaben bei genuegend \ Geduld, was die Ausfuehrzeiten betrifft, schon jetzt in Angriff nehmen. Und \ wem es dabei zu lange dauert: Die ganze Forth-Welt steht einem ja fuer den \ Einbau in ein eigenes automatisierendes Programm offen! \ Hier also (als Alibi fuer die Verwendungsmoeglichkeit) zum Abschluss zwei \ Forth-Worte (mbr>bak und bak>mbr), mit deren Hilfe der MBR (der vom BIOS als \ erste Boot-Platte eingestuften Festplatte) in die Datei MBR.BAK geschrieben \ und von dort auch wieder zurueckgeholt werden kann. Die Datei MBR.BAK braucht \ nicht schon zu bestehen. Ich fasse das Vorhaben zunaechst gleich etwas \ allgemeiner (fuer beliebige Sektoren, nicht nur fuer den MBR) auf: \ Achtung: Fuer ZF sind die jetzt folgenden Worte sect>bak und mbr>bak und \ bak>sect und bak>mbr herauszunehmen (siehe gleichfolgende Bemerkungen)! : sect>bak ( -- ) " sectbuf dup 200 + save sect.bak " $execute ; \ Das reicht an sich. Aber hier noch eine auf den MBR spezialisierte Fassung: : mbr>bak ( -- ) getmbr " sectbuf dup 200 + save mbr.bak " $execute ; \ Einfacher kann es wirklich nicht gehen! Trotzdem, ZF kann mit 'save' nichts \ anfangen. Da wurde nun damals, fast gleichzeitig, an verschiedenen Enden der \ Welt enorme Energie in die Umwandlung von F83 in die Abkehr vom Blocksystem \ hin zur DOS-Dateiverwaltung gesteckt - und dabei hat man vergessen, auf die \ Muttersprache des jeweils anderen Entwicklers einzugehen, ja vom anderen \ Entwickler ueberhaupt Notiz zu nehmen! Es gab keine Kommunikation. Alles, \ was mit der Dateiverwaltung zu tun hat, wurde (beim Uebergang von F83 zu \ Turbo-Forth oder ZF) auf zwei grundverschiedene Arten programmiert. Schade! \ Fuer Turbo-Forth-Fans: Man beachte den Einsatz von $execute ! Das ist eine \ einleuchtende Moeglichkeit, das ganze Hin-und-Her (von ANS-Forth) um compile \ und [compile] und postpone zu umgehen! \ Und schliesslich - ich will ja beispielsweise den MBR backuppen, ohne dazu das \ 180 Megabyte grosse Acronis-TrueImage kaufen, registrieren und ankurbeln zu \ muessen - das Pendant zu mbr>bak zum Zurueckschreiben des MBRs (von Datei \ wieder auf Festplatte). Wie gross ist das Gezeter, wenn sich beispielsweise \ GRUB beim Installieren von Linux (aus Versehen) im MBR eingenistet hat, dort, \ wo eigentlich der Dual-Boot-Manager von XP haette liegenbleiben sollen - oder \ umgekehrt, von Linux zu XP! Also versuche ich mich abschliessend noch an einem \ Forth-Wort bak>mbr, mit welchem ich den zuvor in eine Datei namens mbr.bak \ gespeicherten MBR wieder zurueckschreiben kann. \ Auf Anhieb findet man dazu auch in Turbo-Forth nichts. Nichts von der Art \ eines inversen 'save's, ein auf Dateien bezogenes 'load'. Warum eigentlich \ nicht? Aber gemach! Wenn man in den dateibezogenen Worten von Turbo-Forth \ herumstoebert (ueber HELP und SEE und DUMP), findet man, dass das Wort (get) \ schon fast alles bereitstellt: : bak>sect ( -- ) " open sect.bak >r dsegment sectbuf 200 r> (get) close " $execute nip abort" Fehler!" ; : bak>mbr ( -- ) " open mbr.bak >r dsegment sectbuf 200 r> (get) close " $execute nip abort" Fehler!" putmbr ; \ Vorsicht auch wieder bei Aufruf dieser Worte! Enthaelt die Datei mbr.bak \ tatsaechlich den MBR? Oder/und hat man den MBR sicherheitshalber wenigstens \ noch woanders aufbewahrt? \ Sicherer ist es mit sect>bak und bak>sect (statt mit mbr>bak und bak>mbr). \ sect>bak und bak>sect stellen nur den Datenverkehr vom Puffer sectbuf zur \ Sicherungsdatei und zurueck dar. Gefaehrlich wird es dann erst beim dann \ noetig werdenden Wort (putsect) oder/und putmbr (siehe Teil 1). \ In ZF funktioniert bak>sect und bak>mbr so einfach 'natuerlich' auch wieder \ nicht. Es werden ja einige Turbo-Forth-eigene Spezialitaeten verwendet. Das \ ist aber genau das Dilemma, vor dem man steht, wenn es (wie z.B. auch bei \ ANS-Forth) um Standardisierungen geht: Gerade solche Feinheiten wie die eben \ erwaehnten, von denen die praktikable Einsetzbarkeit von Forth-Programmen in \ erheblichem Masse abhaengen kann, werden von den Standardisierungsempfehlungen \ kaum beachtet. \ ZF-Fans werden sich vielleicht aufgerufen fuehlen, eine ZF-Fassung fuer \ mbr>bak und bak>mbr (oder sect>bak und bak>sect) zu schreiben!