Als ich den Suchbegriff "switch to real mode" an Google schickte, bekam ich
7,5 million Treffer. Ich konnte aber keine genaue Beschreibung finden, wie das
zu tun wäre. Jeder der gefundenen Hinweise war entweder nicht brauchbar, oder
nicht bezogen auf die "Architektur bedingten" Details der Intel-CPU, Typ i486,
oder sogar falsch. Sogar das dicke und teure Buch (vor 30 Jahren geschrieben),
das ich normalerweise als Ausgangspunkt nehme, wenn ich unter die Haube meiner
Maschinen gehe, enthält eine teilweise falsche und nicht richtig begründete
Beschreibung (ausdrücklich auf Intel-Dokumentation basierend!)
So machte ich selber, was offenbar für Millionen Menschen interessant ist. Und
ich beschreibe hier den komplizierten Weg zum ersehnten Zustand "real mode".
Ich mache es online lesbar in .html, damit jedermann leicht zur Lektüre kommt
und beanspruche kein anderes copyright als GNU. Jedem steht völlig frei, meine
Geistesfrüchte zu kosten!
Der nötige Assembler-code wird im NASM-Dialekt und meinem ASMat-Dialekt gezeigt.
Beide Dialekte sind unter meinem Assembler Betriebssystem ASMOS brauchbar, das
NICHT im page mode, aber im protected mode arbeitet.
Sich mit ASMOS vertraut machen:
meine homepage www.rcfriz.de
ÜBERBLICK:
Der erste Schritt muss sein, den page mode los zu werden und zurück in den
normalen protected mode zu kommen, wo Segment-Deskriptoren und Selektoren, die
sie adressieren, zu benutzen sind, um Basis-Adressen zu definieren, unter denen
Basis-Adressen =0 von Programmen nutzbar und verschieblich sind
Dieser Schritt ist mit folgenden zwei Assembler-Kommandos getan:
mov eax,1 Das PE-bit =Einschalten d.protected mode mit bit1
Das PG-bit =Einschalten d.page mode mit bit31
mov cr0,eax
Dieser Schritt kann nicht getan werden, wenn ein Programm nicht in einem Segment
mit Privileg-Stufe 0 läuft. Und natürlich muss man wissen, wo die "Global
Descriptor Table" (="GDT") im Arbeitsspeicher liegt. Das muss eine absolute
Adresse sein, die man mit folgenden Kommandos erfährt:
mov eax,b b=Adresse von Länge und Basis-Adresse der GDT (2*DD)
sgdt .eax speichere Deskriptor der GDT in Variable
Die Variable "b" enthält danach die Länge in bits0-15 und die Basis-Adresse in
Bits16-47. Das gleiche Format wird für den IDT-Deskriptor benutzt, wobei die
Bits48-31 immer =0 sein müssen. Auch diese Kommandos können nicht ohne die
Privilegstufe 0 gegeben werden!
Diese Schritte müssen unter ASMOS nicht getan werden, wo jedes Programm jedes
beliebige Kommando enthalten kann (ausser einigen nicht ursprünglichen i486
Kommandos). Das muss im Hinterkopf gehalten werden, wenn man unten den
Beispiel-Code liest!
Wenn man in Privilegstufe 0 und protected mode ist und weiss wo nicht nur die
GDT, sondern auch die IDT liegen, dann kann man weitere Schritte tun, wenn man
auch weiss, wie das eigene Betriebssystem den Interrupt-Controler initialisiert
hat und wo die zugehörigen Interrupt-Vektoren in der IDT abgelegt wurden.
Diese sehr wichtige Tabelle ist immer in Gebrauch! Aber die CPU macht nach dem
Reset eine Besetzung für den IDT-Deskriptor, die dem originalen AT-Design
entspricht: Länge in Bits0-15 =3FFh und Basis-Adresse =0 .Das ist der nicht
geänderte Ausgangspunkt für das BIOS.
Da im protected mode die ersten Interrupt-Vektoren (unterste Adressen und
Nummern) von der CPU benutzt werden ("faults",traps" usw.), muss jedes
Betriebssystem zuerst nicht nur die Basis-Adresse der IDT ändern, sondern auch
die Interrupt-vektoren - insbesondere jene, die der Interrupt-Controler
braucht.
Diese Details muss wissen, wer zurück in den protected mode will, während man
den originalen, vom BIOS gemachten Zustand kennen muss, bevor man wirklich in
den real mode gelangen kann.
Bevor man irgendwelchen real-mode-Code in den Speicher unter 1M schreiben kann,
muss man alle Daten, die dort liegen, retten. Andernfalls kann man weder in den
real mode noch zurück in den protected mode! Meistens wird man neue Deskriptoren
für GDT und IDT definieren müssen, die im Bereich unter 1M liegen und ohne neue
Basis-Adressen nicht mehr brauchbar sind - die sind zur Programmierzeit nicht
bekannt, sondern ergeben sich nur zur Laufzeit.
Der nächste Schritt besteht in der Definition von einem neuen Deskriptor in der
verschobenen GDT - er beschreibt ein 16-Bit-protected-mode-Segment mit der
gleichen Basis-Adresse wie das beabsichtigte real-mode-Segment. In meinem
Beispiel unten ist dies die absolute Adresse 80000h. Und man muss im
im real-mode-Segment noch ein weiteres Segment für den stack definieren!
Das zu schreibende Binary muss abhängig vom Assembler-Dialekt, den man benutzen
will, programmiert werden. Es gibt viele verschiedene Wege, um Binaries aus
gemischtem Code für drei verschiedene Zustände der Maschine zu schreiben, die
zu durchschreiten sind. Ich werde das unten im Detail erklären...
Das fertig geschriebene Programm hat schliesslich zur Laufzeit weitere Schritte
zu tun, um den Teil, der unterhalb 1M arbeiten soll, fertig zum Gebrauch zu
machen. Mindestens die neuen Deskriptoren für GDT und IDT sind in den Code zu
schreiben. Und natürlich die Zieladresse für den Rücksprung in das
protected-mode-Programm!
Wenn das zu verschiebende Binary komplettiert ist, kann es ab einer absoluten
Adresse unterhalb 1M geschrieben und mit einem FAR-Sprung im
32-Bit-protected-mode gestartet werden. Dieses Kommando hat den neuen Selektor
für das 16-Bit-protected-mode-Segment zu enthalten, das zuvor gemacht wurde.
Man kann auf keinen Fall mit einem FAR-Sprung in das real-mode-Segment kommen,
weil die CPU, nachdem vor dem Sprung der protected mode ausgeschaltet wurde,
eine Segment-Adresse im Kommando erwartet. Im protected mode werden
Offset-Adressen in 32 bits benutzt, die NICHT dem Format im real mode
entsprechen. Das ist der Grund, warum man den 16-Bit-Deskriptor braucht, der
einen 16-Bit-Offset erzwingt für den weiteren Sprung in den real mode.
Im 16-bit-protected-mode-Segment unter 1M hat man vor dem Sprung in den real
mode nicht mehr zu tun als den protected mode abzuschalten. Man muss nicht
Segment-Register-Inhalte oder irgendeinen Stack umdefinieren. Aber den protected
mode innerhalb eines 16-bit-Segmentes abzuschalten, ist ein besonderes Problem,
das ich unten im Detail beschreibe (und als Erster).
Innerhalb des real modes kann man die gut bekannten Schritte tun, um zurück in
den protected mode zu kommen (jeder Bootloader enthält sie). Weil aber nur eine
eine 16-bit-Offset-Adresse im Sprung-Kommando erlaubt ist, kann man Probleme
bekommen beim Rücksprung, deren Lösung ich unten zeige.
IM DETAIL:
Meine Aufgabe war ein Programm, das VBE-Funktionen mit Werten in Registern
zu rufen erlaubt. So rufe ich in meinem Programm eine Prozedur als wenn ich
einen BIOS-int benutzte.
Da ASMOS GDT, IDT, stack und andere wichtige Dinge zwischen den Adressen
1000h und 9FFFFh stapelt, ist die real-mode-int-Tabelle zwar unberührt, alles
andere bis zum Video-Bereich aber zu retten. Das ist leicht getan, weil ASMOS
auf absoluter Adresse die Adresse verfügbaren Speichers über 1M enthält. Diese
Adresse muss im Programm aufbewahrt werden, während die ASMOS-Variable neu
definiert werden muss, weil andere Programmteile freien Platz oberhalb des
geretteten Speicherinhalts benötigen können.
Dieses Beispiel ist im ASMat-Dialekt geschrieben. Da gilt: Präfix ":" leitet
einen Namen eines Labels ein, Präfix " " markiert die folgende Zeichenkette als
Kommando bis zum ersten " " nach erkanntem Code, Kommentare können getrennt
durch " " einem Kommando oder einem Label folgen, oder ohne irgendein Präfix
am Zeilenanfang stehen (Keine Kommentarzeichen!), Operanden-Adressen in
Kommandos stehen nach Präfix ".", der alberne Klammern ersetzt.
-----------------------------------anderer Code
mov edi,.es:100024h Ziel-Adresse="membegin" in ASMOS
...becomes now base-adress of moved low mem!
mov .lowMEM,edi
mov esi,0
call P_Move_mem
-----------------------------------anderer Code
:P_Move_mem call with base-adress of source in esi, of destination in edi
mov ecx,28000h Anzahl der zu schreibenden Doppelworte
:Move_mem
dec ecx
mov eax,.es:esi+ecx*4
mov .es:edi+ecx*4,eax
jne Move_mem
ret
Zu sehen ist, dass ich die Adress-Kalkulation im protected mode nutze, die
C-Programmierern fremd ist und kaum im page-mode nutzbar ist (Man muss absolute
Adressen, Basis-Adressen in Deskriptoren und Selektoren in Segment-Registern
handhaben können - unter ASMOS ist in "es" normalerweise der Überalles-Selektor
mit Basis-Adresse =0 .Ich mache diesen Schritt vor allen anderen unter 1M und
restauriere erst am Ende des Programms. So können GDT, IDT und Stack
umdefiniert werden mit Adressen in Variablen innerhalb des Programms und dem
unter 1M zu versetzenden Binary. Solche Programmierung kann nicht innerhalb
des page modes eines Betriebssystems veranstaltet werden!
Während der Laufzeit dieses Programms benutze ich die verpflanzte GDT, IDT und
Stack, nachdem ich noch folgendes getan habe:
-----------------------------------anderer Code
mov eax,.lowMEM
add esp,eax neuer stackpointer / selector =overall-selector =8
add eax,1000h abs.Basis-Adresse der normalen GDT in ASMOS
mov .gdt1,eax 17-48 DWord aus absoluter Adresse
add eax,10000h abs.Basis-Adresse der normalen IDT in ASMOS
mov .idt1,eax 17-48 DWord aus absoluter Adresse
mov eax,gdt0 erste von 4 Byte-Adressen des Deskriptors
lgdt .eax
mov eax,idt0 erste von 4 Byte-Adressen des Deskriptors
lidt .eax
Definiere die Basis von noch freiem Puffer für weitere Aufgaben:
add edi,A0000h letztes DD des versetzten Codes =9FFFCh, der Stack
mov .es:100024h,edi =neues "membegin"
-----------------------------------anderer Code
Der wie ein BIOS-Int zu rufenden Prozedur steht Code voran, der separat zu
übersetzen ist und als Binärcode eingefügt werden muss, bevor dieses Programm
übersetzt werden kann. Während das Programm mit ASMat zu übersetzen ist, wird
der zu versetzende Code mit ASMnr übersetzt, das nur real-mode-Code übersetzt
und den selben Dialekt wie NASM benutzt. Meine Übersetzungsprogramme erlauben
keinen gemischten Code (macht sie schneller). Normalerweise braucht man
Ausdrücke wie "USE 16" oder "BIN 16" oder irgend ähnliches vor solchem Code. Im
ASMat-Dialekt ist das folgende Kommentar, der kopiert, übersetzt und als Binary
wieder kopiert und weiter unten als binäre Konstante eingefügt wird. Unter
ASMOS sind diese Aktionen sehr einfach zu handhaben.
_Wir sind zunächst im 16-bit-protected-mode-Segment.
_ jmp START
_ nop
_;Diese Pufferzone speichert Basis-Adressen von GDT und IDT und die vom Rufer
_;gegebenen Registerinhalte. Nach Abläufen im real mode können die Register
_;umdefiniert sein und ihre neuen Inhalte können hier wieder auf absoluten,
_;dem Rufer zugänglichen Adressen gespeichert werden.
_RegAX: DW 0 ; Adresse:4 / ...in je 2 Bytes ax,bx,cx,dx
_RegBX: DW 0
_RegCX: DW 0
_RegDX: DW 0
_IDT0: DW 0FFFFh ; Adresse:0Ch / IDT im protected mode / 1-16 Länge
_IDT1: DW 0 ; Adresse:0Eh / 48-bit-pointer auf IDT im oberen Speicher
_IDT2: DW 0
_IDT3: DW 0 ; immer =0
_IDTrm0: DW 003FFh ; Adresse:14h / IDT im real mode / 1-16 Länge
_IDTrm1: DW 0 ; Adresse:16h / 48-bit-pointer to IDT in low memory
_IDTrm2: DW 0
_IDTrm3: DW 0 ; immer =0
_START:
_;Hier sind 16-bit-register-Teile normal, man braucht ein "operand-size-praefix
_;=66h um "xor ax,ax" wie "xor eax,eax" wirken zu lassen, was genau anders rum
_;im 32-bit-protected-mode geht. Dann fügen die meisten Assembler-Übersetzungs-
_;Programme automatisch das operand-size-praefix vor "xor ax,ax" ein, während
_;der Opcode der selbe wie bei "xor eax,eax" ist. Das muss interessieren, wenn
_;man eine Übersetzung für den real mode hier macht.
_;Es gibt keinen Grund hier irgendwelche Segment-Register zu laden, weil wir
_;sofort in den real mode wechseln. Wir schalten nur den protected mode (und
-;den page mode) ab.
_;WARNUNG!
_;Das Kommando "lmsw" arbeitet NICHT im 16-bit-protected mode !!!!!
_;Da die meisten Übersetzungs-Programme (wie mein ASMnr) für den real mode weder
_;das Kommando "mov cr0,eax" übersetzen noch "mov eax,60000000h" mit richtiger
_;Länge von 32 Bit für den immediate Wert, definiere ich die Opcodes binär als
_;Konstanten (Natürlich müssen solche Werte in der richtigen Abfolge von
_;Kommandos stehen!).
_Rmop_mov: DW B866h ; operand-size-praefix 66h erzwingt Laden von eax statt
_ ; des "normalen" ax im 16-Bit-Pmode. "B8h" is the opcode.
_Rmimm_movL: DW 0 ;="mov eax,60000000h"
_Rmimm_movH: DW 6000h ; Wert in eax schaltet den cache und das PE-bit aus
_Rmop: DB 0FH ; Opcode von "mov cr0,eax" / benutzt alle 32 Bits
_ ; unabhängig von der Operanden-Breite!
_;Nach diesem Schalten erwartet die CPU eine Segment-Adresse in cs !
_;Da Ich weiss, wo das Segment sein soll, kann ich es vor der Laufzeit geben
_;und die Übersetzung den Rest machen lassen...
_ jmp 8000h:Realmode ; real-mode-Segment und KEIN Selektor!
_;Der Offset wird in 16 Bits stehen, weil für den real mode übersetzt wird.
_;Erst nach Benutzung von Segment-Registern wird tatsächlich von der CPU der
_;gewechselte Zustand erkannt! Dann haben alle anderen Register ebenfalls
_;Segment-Adressen zu enthalten statt Selektoren.
_Realmode:
_ mov ax,8000h
_ mov ds,ax
_ mov ax,8800h ; Puffer-Zone für video-BIOS, lesbar für den
_ ; Rufer auf abs.Adresse 88000h
_ mov es,ax ; die meisten BIOSints erwarten die Puffer-
_ mov di,0 ; Definition in es:di
_ mov ax,9000h
_ mov ss,ax
_ mov sp,8000h ; wird dekrementiert...
_;Definiere die real mode IDT als BIOS-default:
_ mov si,IDTrm0
_ lidt [si]
_;Initialisiere den Interrupt-Controller wie BIOS (KEIN eoi wie unter ASMOS)
_;Interrupt-Tabelle und Vektoren ab Adresse=0 bleiben unter ASMOS unberührt!
_;Maskiere alle ints: ; Nicht alle sind unter ASMOS maskiert
_ mov al,FFh ; De-maskieren mit 0, maskieren mit 1 / Bits sind auf
_ ; IRQ# bezogen
_ out 21h,al ; Master
_ out 0A1h,al ; Slave
_;Init, 1.Parameter:
_ mov eax,00010001b
_ out 20h,al;Master
_ out 0A0h,al ; Slave
_;Init, 2.Parameter: (BIOS default ist unter ASMOS geändert)
_ mov al,8 ; setze INT# auf IRQ#0 des Masters
_ out 21h,al
_ mov eax,70 ; dezimal!
_ out 0A1h,al ; setze INT# auf IRQ#0 des Slave
_;Init,3.Parameter:
_ mov eax,00000100b
_ out 21h,al ; IRQ# des Slave, bit-orientiert an Master
_ mov al,00000010b
_ out 0A1h,al ; IRQ# des Slave an Master-Inputs, dual codiert an Slave
_;Init, 4.Parameter: ( Bit1: 1=auto, 0=eoi explizit)!
_ mov al,1 ; means: Intel-Umgebung
_ out 21h,al ; BIOS default an Master
_ mov al,1
_ out 0A1h,al ; BIOS default an Slave
_;Mache einen "unspecific end of interrupt": (im real mode nach jedem Int nötig)
_ mov al,20h
_ out A0h,al
_ out 20h,al
_;De-maskiere alle ints: (meistens maskiert unter ASMOS!)
_ mov al,0 ; De-maskieren mit 0, maskieren mit 1 / Bits sind auf
_ ; IRQ# bezogen
_ out 21h,al ; Master
_ out 0A1h,al ; Slave
_;Restaurieren der Inhalte von Registern, die der Rufer definierte:
_ mov ax,[RegAX]
_ mov bx,[RegBX]
_ mov cx,[RegCX]
_ mov dx,[RegDX]
_ sti
_ int 10h ; Nur ein Beispiel....Beliebig anderes darf sein.
_ mov [RegAX],ax
_ mov [RegBX],bx
_ mov [RegCX],cx
_ mov [RegDX],dx
_ push 0
_ popf ; clear interrupts vor der Rückkehr...
_;Zurück zum Rufer im 32-bit-protected mode mit ASMOS-Standard:
_;Maskiere ints:
_ mov al,10001b
_ out 21h,al
_ out 0A1h,al
_;Init, 1.Parameter:
_ mov eax,10001b
_ out 20h,al
_ out 0A0h,al
_;Init, 2.Parameter:
_ mov al,0F0h
_ out 21h,al
_ mov eax,0F8h
_ out 0A1h,al
_;Init,3.Parameter:
_ mov eax,100b
_ out 21h,al
_ mov al,10b
_ out 0A1h,al
_;Init, 4.Parameter: ( bit1=1=auto-eoi)!
_ mov al,11b
_ out 21h,al
_ mov al,11b
_ out 0A1h,al
_;Etabliere IDT:
_ mov si,IDT0
_ lidt [si]
_ mov ax,[Selector32] ; hol den Programm-Selektor für direkten Sprung
_ mov [FARjmpSELrm],ax
_;Schalte zum protected mode (cli, switch PE-bit im machine status word und
_;springe FAR auf folgenden Code):
_;Da dies für den real mode übersetzt wird, arbeitet dort auch folgendes
_;Kommando...
_ mov ax,1
_ lmsw ax ; set PE-Bit in cr0
_;FAR Sprung mit Selektor:Offset-argument. Erst dieser Sprung schaltet den
_;protected mode wegen der Benutzung von Segment-Registern wirklich ein!
_;Ein indizierter Sprung würde hier nur arbeiten, wenn auch noch ein passender
_;Selektor in ds geladen würde!
_;Einfacher ist ein direkter Sprung, gemacht aus Konstanten.
_FARjmpOPrm: DB EAh ; Opcode des direkten FAR jump
_FARjmpOFrm: DW 32Pmode ; Offset 0-15 des Sprungzieles
_FARjmpSELrm: DW 0 ; Selektor zum 32-bit-Deskriptor
_32Pmode: ; Nun sind wir im 32-bit-protected-mode-Segment
_ mov ax,8
_ mov ds,ax ; overall-segment
_ mov es,ax ; overall-segment
_ mov fs,ax ; overall-segment
_ mov gs,ax ; overall-segment
_ mov ss,ax ; overall-segment
_;Erst jetzt kann der FAR-Sprung zum 32-bit-protected mode über 1M getan werden.
_;Da das Kommando von ASMnr mit einer Offset-Adresse in 16 Bit statt der
_;nötigen 32 Bit übersetzt würde, wird hier der Opcode als Konstante gegeben.
_FARjmpre: DB 66h ; operand-size-praefix 66h erlaubt 32 bit Offset
_FARjmpOP: DB 0EAh ; Opcode des direkten FAR-Sprunges
_FARjmpOF0: DW 0 ; Offset 0-15 der Sprungziel-Adresse
_FARjmpOF1: DW 0 ; Offset 16-31 der Sprungziel-Adresse
_FARjmpSEL: DW 0 ; Selektor zum 32-Bit-Deskriptor
_Selector32: DW 0 ; Selektor zum 32-Bit-Deskriptor, der das zum
_ ; real-mode-Segment kongruente Segment definiert.
_PROGAMMend: DB "END " ; Diese Zeichen braucht man unter ASMOS, um das
_;Kopieren zu vereinfachen. Erstes nicht zu kopierendes Zeichen ist das "E".
Nach der Übersetzung des obigen Codes wird das Binary in die unten stehende
Konstante kopiert - ein spezieller Typ, der von ASMn und ASMat erkannt wird.
Wer andere Übersetzungs-Programme und Assembler-Dialekte zur Programmierung
dieses Ablaufes benutzen will, braucht einen äquivalenten Typ einer Konstante,
die das Binary bei einem Label beginnen und exakt vor dem folgenden enden lässt.
Auf diese Weise werden Adressen im Binary zur Laufzeit unten adressierbar -
das ist in diesem Zusammenhang unverzichtbar!
Der originale NASM-Assembler bietet als ziemlich ähnlichen Typ "INCBIN"...
:16-bit-PROTECTEDM i 63h first byte of binary at "f", last at "d" of end
Das folgende Label der Prozedur bezeichnet die nächste Adresse über dem "d".
Sie wird gerufen nach Definition von Registern in Bits 0-15 und nach versetzen
des Codes unter 1M (Vor und nach diesem Ruf mit Nutzung der oben gezeigten
Prozedur "P_Move_mem".
:P_VBE-call
mov .callerESP,esp originalen stack-pointer retten
In es:di zu definierende Puffer-Zone ist immer bei abs. 88000h (es=8800h,edi=0)
mov esi,16-bit-PROTECTEDM Vom Rufer definierte Register-Inhalte
retten. Kein push&pop ist hier erlaubt, weil sich der Stack ändert!
mov .esi+4,eax
mov .esi+6,ebx
mov .esi+8,ecx
mov .esi+Ah,dx
Definiere Konstanten im Code für 16-bit-protected & real mode:
Die folgenden Adressen sind bezogen auf das Label am Beginn des übersetzten
Binary!
mov eax,.lowMEM
add eax,11000h Adresse der neuen IDT im oberen Speicher
mov .esi+Eh,eax
Die folgenden Adressen sind bezogen auf das Label hinter dem Ende des
übersetzten Binary!
mov edi,P_VBE-call
Definiere Pointer im FAR-Sprung zurück zum 32-bit-protected-mode:
mov .edi-8h,eax
mov eax,cs Selektor im Opcode hier
mov .edi-4,ax
Wir machen nun ein Programm-Segment unter 1M mit Operanden- und Adress-Breite
von 16 Bit. Ein Programm-Daten-Segment ist NICHT nötig.
Basis-Adresse des Segmentes ist 80000h. Die Abfolge der Deskriptoren im Stapel
der GDT steht im Raster von 8 Byte-Adressen.
Innerhalb von ASMOS gibt es eine Variable namens "GDTtop", die die aktuelle
Offset-Adresse der ersten verfügbaren enthält, über den bereits definierten
Deskriptoren. Dies ist tatsächlich auch der Wert eines Selektors, wenn die
Bits0-2 =0 sind. Sie definieren Privilegstufen und werden unter ASMOS nicht
genutzt, sondern sind immer =0
Man braucht den neuen Deskriptor nicht in der Abfolge der vorhandenen. Man
muss nur sicher gehen, dass man nicht bereits definierte überschreibt!
Der hier gemachte Deskriptor kann nämlich vergessen werden bei der
Rückkehr von dieser Prozedur...
Die absolute Adresse der neuen GDT ist oben bereits definiert...
mov edx,.gdt1 absolute Adresse d. GDT oberhalb 1M
mov eax,.es:100038h ="GDTtop"
cmp eax,FFF8h
jc Progdescon
Das folgende bewirkt unter ASMOS eine Fehlerausgabe auf dem Bildschirm.
mov ebx,2 "OUT OF RANGE"
stc Vom Rufer der Prozedur zu verwerten...
ret
:Progdescon
add edx,eax Selektor von 16-bit-Programm-Deskriptor
mov w.es:edx,FFFFh 1-16 Länge
mov w.es:edx+2,0 1-16 Segment-Adresse
mov b.es:edx+4,8 17-24 Segment-Adresse
mov b.es:edx+5,10011010b Bitfeld
mov b.es:edx+6,0 definiert 16-bit-Deskriptor
mov b.es:edx+7,0
Dieser Deskriptor ist vergessen nach Rückkehr von der Prozedur, weil "GDTtop"
nicht inkrementiert wird. Wir brauchen aber den aktuellen Wert, der der Selektor
des eben gefertigten Programm-Deskriptors für ein 16-bit-Segment ist.
mov eax,.es:100038h ="GDTtop"
Programm-Selektor im FAR-Sprung zum 16-bit-protected mode
mov .FARjmpS,ax
mov .edi-2,ax Wert von "FARjmpSELrm" im Binary
Versetzen des Binaries in den unteren Speicher-Bereich:
mov ecx,edi
mov edi,80000h Ziel der Versetzung
Länge des zu versetzenden Codes kalkulieren:
sub ecx,esi
shr ecx,2
inc ecx
:MoveRM
dec ecx
mov eax,.esi+ecx*4
mov .es:edi+ecx*4,eax
jne MoveRM
Puffer für BIOS vorbesetzen mit 0 :
mov edi,88000h Puffer-Basis-Adresse
xor eax,eax
mov ecx,8000h
:ClearRM
dec ecx
mov .es:edi+ecx*4,eax
jne ClearRM
push 0
popfd Reset EEFLAGS, meint auch "cli"
mov eax,60000001h der Wert schaltet den Cache aus,
berührt aber nicht das PE-bit (immer noch im protected mode).
mov cr0,eax
jmp f.FARjmpO
:FARjmpO d 0 offset 0-32 of destination label
:FARjmpS w 0 selector of 16-bit-descriptor
a align with nops....
:BackAgain Wir sind zurück im 32-bit-protected mode über 1M....
mov eax,cs
add eax,8 Standard unter ASMOS: Der kongruente
Daten-Programm-Deskriptor ist stets über dem Programm-Deskriptor definiert.
mov ds,eax Selektor des Programm-Daten-Segmentes
mov esp,.callerESP hält die Rücksprung-Adresse d.Prozedur!
mov eax,1
Setting in cr0: PE=H=an, CD,NW=L=cache an
mov cr0,eax
sti wieder Interrupts erlauben
ret
Wenn das Betriebssystem im page mode läuft, muss man noch weitere Schritte
zurück zu jener Steinzeit tun...
Der page mode wurde einst in die Welt gesetzt, weil damals Arbeitsspeicher noch
sehr klein und sehr teuer war. Tatsächlich reduziert er die Ausführungszeit
aller Programme um mindestens die Hälfte. Auch multi-tasking wurde nur in die
Welt gesetzt, weil die CPUs noch sehr langsam waren. Multi-tasking reduziert
die Ausführungszeit noch weiter. Ganz schlimm wird's bei swapping...
Moderne Computer enthalten bis zum Faktor 1000 schnellere CPUs und bis zum
Faktor 2000 mehr Platz im Arbeitsspeicher. Das war einst unerschwinglich oder
garnicht machbar. Wer also heute page mode und multi-tasking als Vorteil preist,
ist nicht bei Verstand...