sabine hat geschrieben:Woran könnte ich einen "versteckten Grafikmode" erkennen, falls es doch kein Textmode wäre?
Kommt drauf an. Man kann zwar im Textmode auch die "9. Pixelspalte" der Buchstaben ausschalten, aber standardmäßig macht das kaum einer. Im Grafikmode wird, der Einfachheit halber, meist mit 8 (statt 9) Pixel breiten Buchstaben gearbeitet. Das wäre so ein grober Anhaltspunkt. Aber - man kann einen Grafikmodus auch genau wie einen Textmodus aussehen lassen.
"Normalen" Textmode kann man erkennen, indem bei $0040:$004B eine 3 (16farb) oder 7 (Herc-Mono) steht - ein sicherer Garant ist das aber auch nicht - das gilt nur, wenn der Mode per BIOS-Funktionen gesetzt wurde. (Man kann die Register der Grafikkarte bekanntlich auch komplett manuell beschreiben.)
Grafikmodus speichert seine Daten ab $A000:$0000 (außer CGA), Textmodus ab $B800:$0000 (wenn 16farb EGA/VGA) oder $B000:$0000 (wenn Hercules-Mono). Sollte der Grafikmode über VESA gesetzt sein, sind die Abfragen da noch etwas anders... Naja, es ist 'ne Wissenschaft für sich, weil durch die vielen Neuerungen damals ein ziemliches Gefrickel entstanden ist. Wenn man Pech hat, haben die Programmierer des betreffenden Programms da etwas komplett eigenes hingeschustert (selten, kommt aber vor) - dann hilft nur Ausprobieren.
sabine hat geschrieben:und ja, wäre super wenn Du das einklinken in den Timer-IRQ auch noch beschreiben könntest
Was ich jetzt schreibe, gilt für den 16bit Mode (Real Mode und V86-Mode) :
Eigentlich ist es simpel. Die zu IRQ0 bis IRQ7 gehörenden ISRs liegen an INT8 bis INT15 (dezimal) und die zu IRQ8 bis IRQ15 (dezimal) liegen an INT128 (dez.) bis INT135 (dez.)
Der Ticker ist hardwaremäßig an IRQ0 gelegt, sein Software-INT (ISR = Interrupt Service Routine) liegt also an INT8. Dieser wird standardmäßig ca. 18,2x pro Sekunde aufgerufen (Aufruffrequenz kann man ändern, wäre für die vorliegende Aufgabe aber nicht nötig). Dieser Aufruf eines INT sichert die derzeitige Position im Programm (also Register CS und IP) und das Statusregister auf dem Stack und springt dann zu der für INT8 in der Interruptsprungtabelle angegebenen Adresse (die ebenfalls aus Segment und Offset besteht, d.h. lädt daraus IP und CS). Diese Interruptsprungtabelle hat 256 Einträge (sog. "Interruptvektoren") und liegt ab $0000:$0000, also direkt am Speicheranfang und jeder Eintrag braucht 4 Bytes (bzw 2 Words). Ist eine Interrupt-Routine beendet, wird mit IRET zurückgesprungen - dies holt Statusregister und Position zurück und fährt an der aufgerufenen Stelle im Programm fort, als wäre nichts gewesen.
Das war jetzt etwas lang - aber diese Erklärung war nötig, um das Folgende zu verstehen.
Das "Einklinken" funktioniert nun so, daß man den in der Interruptsprungtabelle eingetragenen Sprungvektor erst mal irgendwohin sichert, dann durch die Adresse ersetzt, wo die eigene Routine liegt, die das macht, was man im Ticker 18,2x pro Sekunde ausführen will. Am Ende macht man keinen IRET, sondern springt zu der vorher "irgendwohin" gespeicherten Adresse. Dieses Einklinken macht man einmalig - danach "ist der Vektor verbogen" auf die eigene Routine - diese wird dann immer zuerst ausgeführt, dann zur ursprünglichen Routine gesprungen (die ja auch abgearbeitet werden muß - den Ticker gibt's ja nicht umsonst).
Achja, wie springt man an eine "far" Adresse? Am einfachsten, indem man Segment und Offset (in dieser Reihenfolge!) auf den Stack schiebt und danach RETF ausführt.
Um wieder "auszuklinken", schreibt man einfach die "irgendwohin gesicherte" Adresse wieder zurück an die Stelle in der Vektortabelle. (Die Stelle ist in dem Fall übrigens $0000:$0020, weil INT8 und 4*8=32, also hex $20.)
WICHTIG: Bevor man IRGENDWAS an der Tabelle murkst immer mit CLI die Interrupts kurz sperren, danach dann mit STI Interrupts wieder zulassen. Das muß sein, weil ja theoretisch während des Änderns an der Vektortabelle ein Interrupt passieren kann, der dann vielleicht auf eine "halb geänderte" Adresse zugreift.
Ich hoffe, ich habe das einigermaßen verständlich erklärt.
sabine hat geschrieben:Gibt es zu der TSR-Programmierung Literatur die Du evtl. empfehlen könntest?
Eigentlich habe ich mein Wissen dazu eher aus irgendwelchen Tutorials, als Foren, aus der SWAG und RBIL.
Ich programmiere ja auch eigentlich in Turbo-Pascal, mit Assembler drin. Da würde ich im Falle einer TSR alles in Assember machen und am Ende in Pascal den KEEP-Befehl benutzen. Der beendet ein Programm, ohne es aus dem Speicher zu entfernen (das ist es ja, was eine TSR ist: Terminate, Stay Resident).
Bislang habe ich noch nichts programmiert, um eine einmal gestartete TSR wieder zu entfernen. Ich weiß, daß es gehen muß, habe mich damit aber noch nicht so sehr beschäftigt.
[quote="sabine"]Woran könnte ich einen "versteckten Grafikmode" erkennen, falls es doch kein Textmode wäre? [/quote]
Kommt drauf an. Man kann zwar im Textmode auch die "9. Pixelspalte" der Buchstaben ausschalten, aber standardmäßig macht das kaum einer. Im Grafikmode wird, der Einfachheit halber, meist mit 8 (statt 9) Pixel breiten Buchstaben gearbeitet. Das wäre so ein grober Anhaltspunkt. Aber - man kann einen Grafikmodus auch genau wie einen Textmodus aussehen lassen.
"Normalen" Textmode kann man erkennen, indem bei $0040:$004B eine 3 (16farb) oder 7 (Herc-Mono) steht - ein sicherer Garant ist das aber auch nicht - das gilt nur, wenn der Mode per BIOS-Funktionen gesetzt wurde. (Man kann die Register der Grafikkarte bekanntlich auch komplett manuell beschreiben.)
Grafikmodus speichert seine Daten ab $A000:$0000 (außer CGA), Textmodus ab $B800:$0000 (wenn 16farb EGA/VGA) oder $B000:$0000 (wenn Hercules-Mono). Sollte der Grafikmode über VESA gesetzt sein, sind die Abfragen da noch etwas anders... Naja, es ist 'ne Wissenschaft für sich, weil durch die vielen Neuerungen damals ein ziemliches Gefrickel entstanden ist. Wenn man Pech hat, haben die Programmierer des betreffenden Programms da etwas komplett eigenes hingeschustert (selten, kommt aber vor) - dann hilft nur Ausprobieren.
[quote="sabine"]und ja, wäre super wenn Du das einklinken in den Timer-IRQ auch noch beschreiben könntest[/quote]
Was ich jetzt schreibe, gilt für den 16bit Mode (Real Mode und V86-Mode) :
Eigentlich ist es simpel. Die zu IRQ0 bis IRQ7 gehörenden ISRs liegen an INT8 bis INT15 (dezimal) und die zu IRQ8 bis IRQ15 (dezimal) liegen an INT128 (dez.) bis INT135 (dez.)
Der Ticker ist hardwaremäßig an IRQ0 gelegt, sein Software-INT (ISR = Interrupt Service Routine) liegt also an INT8. Dieser wird standardmäßig ca. 18,2x pro Sekunde aufgerufen (Aufruffrequenz kann man ändern, wäre für die vorliegende Aufgabe aber nicht nötig). Dieser Aufruf eines INT sichert die derzeitige Position im Programm (also Register CS und IP) und das Statusregister auf dem Stack und springt dann zu der für INT8 in der Interruptsprungtabelle angegebenen Adresse (die ebenfalls aus Segment und Offset besteht, d.h. lädt daraus IP und CS). Diese Interruptsprungtabelle hat 256 Einträge (sog. "Interruptvektoren") und liegt ab $0000:$0000, also direkt am Speicheranfang und jeder Eintrag braucht 4 Bytes (bzw 2 Words). Ist eine Interrupt-Routine beendet, wird mit IRET zurückgesprungen - dies holt Statusregister und Position zurück und fährt an der aufgerufenen Stelle im Programm fort, als wäre nichts gewesen.
Das war jetzt etwas lang - aber diese Erklärung war nötig, um das Folgende zu verstehen.
Das "Einklinken" funktioniert nun so, daß man den in der Interruptsprungtabelle eingetragenen Sprungvektor erst mal irgendwohin sichert, dann durch die Adresse ersetzt, wo die eigene Routine liegt, die das macht, was man im Ticker 18,2x pro Sekunde ausführen will. Am Ende macht man keinen IRET, sondern springt zu der vorher "irgendwohin" gespeicherten Adresse. Dieses Einklinken macht man einmalig - danach "ist der Vektor verbogen" auf die eigene Routine - diese wird dann immer zuerst ausgeführt, dann zur ursprünglichen Routine gesprungen (die ja auch abgearbeitet werden muß - den Ticker gibt's ja nicht umsonst).
Achja, wie springt man an eine "far" Adresse? Am einfachsten, indem man Segment und Offset (in dieser Reihenfolge!) auf den Stack schiebt und danach RETF ausführt.
Um wieder "auszuklinken", schreibt man einfach die "irgendwohin gesicherte" Adresse wieder zurück an die Stelle in der Vektortabelle. (Die Stelle ist in dem Fall übrigens $0000:$0020, weil INT8 und 4*8=32, also hex $20.)
WICHTIG: Bevor man IRGENDWAS an der Tabelle murkst immer mit CLI die Interrupts kurz sperren, danach dann mit STI Interrupts wieder zulassen. Das muß sein, weil ja theoretisch während des Änderns an der Vektortabelle ein Interrupt passieren kann, der dann vielleicht auf eine "halb geänderte" Adresse zugreift.
Ich hoffe, ich habe das einigermaßen verständlich erklärt.
[quote="sabine"]Gibt es zu der TSR-Programmierung Literatur die Du evtl. empfehlen könntest?[/quote]
Eigentlich habe ich mein Wissen dazu eher aus irgendwelchen Tutorials, als Foren, aus der SWAG und RBIL.
Ich programmiere ja auch eigentlich in Turbo-Pascal, mit Assembler drin. Da würde ich im Falle einer TSR alles in Assember machen und am Ende in Pascal den KEEP-Befehl benutzen. Der beendet ein Programm, ohne es aus dem Speicher zu entfernen (das ist es ja, was eine TSR ist: Terminate, Stay Resident).
Bislang habe ich noch nichts programmiert, um eine einmal gestartete TSR wieder zu entfernen. Ich weiß, daß es gehen muß, habe mich damit aber noch nicht so sehr beschäftigt.