Copyright (c) 1998 by Michael Neumann, neumann@s-direktnet.de, www.s-direktnet.de/homepages/neumann; für Watcom C++ 11.0

Der Protected-Mode (ab 80386)

 

 

·       DOS-Extender arbeiten als quasi-Betriebssystem und sind daher langsamer als wenn man selber diese Funktionen programmiert, die der DOS-Extender bereitstellt.

·       FAR-Zeiger nehmen jetzt 6 Bytes ein (2 Bytes für Selektor und 4 Bytes für Offset). 

·       Wenn das Paging ausgeschaltet ist, stimmt die lineare Adresse mit der physikalischen überein.

·       Im Protected-Mode ist es möglich IO-Ports zu sperren.

·       Der Virtual-86-Mode ist ein Kompromiß zwischen dem Real-Mode und dem Protected-Mode. Mit dem V86-Mode können DOS-Programme unter Protected-Mode ganz normal ablaufen.

 

 

Aufbau eines Selektors im Protected-Mode:

 

Offset

Größe

Zweck

0

WORD

Segmentlänge (Bits 0-15)

2

WORD

Basisadresse (Bits 0-15)

4

BYTE

Basisadresse (Bits 16-23)

5

BYTE

Flags #1

6

BYTE

Flags #2

7

BYTE

Basisadresse (Bits 24-31)

 

Flags #2:

                        Bits 0-3:           Segmentlänge (Bits 16-19)

                        Bit 5:                0

                        Bit 7:                Faktor für Segmentlänge ( 0 = 1 Byte / 1 = 4KB )

 

 

 

 

 

 

 


 DOS/Extender-Interrupts im Protected-Mode

 

Infos über Interrupt 31h: Programmer’s Guide - Interrupt 31H DPMI Functions

Infos über C-Funktionen: Watcom C Library Reference Help

 

 

 

Interrupt 21h:

Header-Dateien ( dos.h / stdio.h / io.h / stdlib.h )

 

        Interrupt-Vektor setzen:

            Eingabe:

            ah                    = 25h

            al                     = Nummer des Interrupts

            ds :[edx]           = Adresse der neuen Interrupt-Routine

            C:                    _dos_setvect

 

        Interrupt-Vektor ermitteln:

            Eingabe:

            ah                    = 35h

            al                     = Interruptnummer

            Ausgabe:

            es:[ebx]            = Adresse der Interrupt-Routine

            C:                    _dos_getvect

 

        Datei erstellen:

            Eingabe:

            ah                    = 3Ch

            cx                    = Attribut ( normalerweise 0 / siehe TASM-Buch S.426 )

            ds:[edx]            = Zeiger auf Dateiname

            Ausgabe:

            cf                     = Fehler

            eax                  = Handle oder Fehlercode

            C:                    creat / _dos_creat

 

        Datei öffnen:

            Eingabe:

            ah                    = 3Dh

            al                     = Zugriffscode ( 0=read only / 1=write only / 2=both )

            ds:[edx]            Zeiger auf Dateiname

            Ausgabe:

            cf                     = Fehler

            eax                  = Handle oder Fehlercode

            C:                    open / _dos_open

 

        Datei schließen:

            Eingabe;

            ah                    = 3Eh

            ebx                  = Handle

            Ausgabe:

            cf                     = Fehler

            eax                  = Fehlercode ( 6 = ungültiges Handle )

            C:                    close / _dos_close

 

 

        Datei lesen:

            Eingabe:

            ah                    = 3Fh

            ebx                  = Handle

            ecx                  = Anzahl Bytes

            ds:[edx]            = Adresse des Puffers

            Ausgabe:

            cf                     = Fehler

            eax                  = Anzahl gelesener Bytes oder Fehlercode

            C:                    read / _dos_read

           

        Datei beschreiben:

            Eingabe:

            ah                    = 40h

            ebx                  = Handle

            ecx                  = Anzahl Bytes

            ds:[edx]            = Adresse des Puffers

            Ausgabe:

            cf                     = Fehler

            eax                  = Anzahl geschriebener Bytes oder Fehlercode

            C:                    write / _dos_write

 

        Datei löschen:

            Eingabe:

            ah                    = 41h

            ds:[edx]            = Zeiger auf Dateiname

            Ausgabe:

            cf                     = Fehler

            eax                  = Fehlercode

            C:                    remove

           

        Dateizeiger bewegen:

            Eingabe:

            ah                    = 42h

            al                     = Modus ( 0=absolut / 1=akt.Pos / 2=vom Ende )

            ebx                  = Handle

            ecx                  = Position ( Bit 16-31 )

            edx                  = Position ( Bit 0-15 )

            Ausgabe:

            cf                     = Fehler

            eax                  = Fehlercode

            edx                  = neue Pos. des Dateizeigers ( Bit 16-31 )

            eax                  = neue Pos. des Dateizeigers ( Bit 0-15 )

            C:                    lseek

 

        Dateilänge ermitteln:

            Eingabe:

            ax                    = 4202h

            ebx                  = Handle

            ecx,edx                        = 0

            Ausgabe:

            cf                     = Fehler

            edx                  = Dateilänge ( Bit 16-31 )

            eax                  = Dateilänge ( Bit 0-15 ) oder Fehlercode

            C:                    filelength

 

 

 

            DOS-Speicher anfordern:

            ( nur Low-Memory - nur bis 64K Größe )

            Eingabe:

            ah                    = 48h

            bx (ebx ???)      = Größe in Paras

            Ausgabe:

            cf                     = Fehler

            eax                  = Segmentadresse oder Fehlercode

            bx (ebx ???)      = maximale Blockgröße (wenn cf=1)

            C:                    _dos_allocmem

           

        DOS-Speicher freigeben:

            ( wie DOS-Speicher anfordern )

            Eingabe:

            ah                    = 49h

            es                    = Segmentadresse des Speicherblocks

            Ausgabe:

            cf                     = Fehler

            eax                  = Fehlercode

            C:                    _dos_freemem

 

        Programm beenden:

            Eingabe:

            ah                    = 4Ch

            al                     = Exitcode

            C:                    exit

 

 

Interrupt 31h:

Header-Dateien ( stdlib.h )

 

        linearen Speicher anfordern:

            ( wenn VMM aktiv, dann sollte in 4KB Schritten allociert werden )

            Eingabe:

            ax                    = 0501h

            bx:cx                = Größe des Speicherblocks

            Ausgabe:

            cf                     = Fehler

            ax                    = Fehlercode

            bx:cx                = lineare Adresse

            si:di                  = Speicher-Handle

            C:                    malloc

 

        linearen Speicher freigeben:

            Eingabe:

            ax                    = 0502h

            si:di                  = Speicher-Handle

            Ausgabe:

            cf                     = Fehler

            ax                    = Fehlercode

            C:                    free     

           


Watcom 32-Bit Assembler Programmierung

(näheres siehe Watcom C/C++ User’s Guide - 32-bit Assembly Language Considerations)

 

Standartübergabe an externe Assembler-Funktionen: (andere Übergabe siehe S.6-8)

 

Register-Übergabe-Parameter:

 

Typ

„sizeof“ Typ

Argument Größe

Register

char

1

4

Exx

short int

2

4

Exx

int

4

4

Exx

long int

4

4

Exx

float

4

8

EDX:EAX || ECX:EBX

double

8

8

EDX:EAX || ECX:EBX

near pointer

4

4

Exx

far pointer

6

8

EDX:EAX || ECX:EBX

Exx Reihenfolge: EAX  EDX  EBX  ECX

 

Stack-Übergabe-Parameter:

 

Beispiel:

            push     EBP

            mov      EBP,ESP

           

            ; Hier dann Zugriff über [EBP+12] für den ersten Parameter, [EBP+16] für den zweiten.

 

            mov      ESP,EBP

            pop       EBP

            ret

 

Rückgabe-Werte:

 

Register:

1 Byte, 2 Byte, 4 Byte, 8 Byte - AL, AX, EAX, EDX:EAX

 

ansonsten auf Stack:

Beispiel:

            ;struct int_values { int value1, value2, value3;};

            RetX proc far                                        ;C:  struct int_values RetX()

             mov     dword ptr SS:0[ESI],71              ; value1

             mov     dword ptr SS:4[ESI],72              ; value2

             mov     dword ptr SS:8[ESI],73              ; value3

            ret

 

 

·       Wenn die Parameter nicht mehr in die oben genannten Register passen, werden sie in Schritten von 4 Byte auf den Stack abgelegt.

·       Alle Register, außer die,  in denen die Parameter waren, müssen gesichert und restauriert werden. Die Register EAX, ECX und EDX müssen nie gesichert werden.

·       Das Direction-Flag muß beim Rückkehr ins Hauptprogramm gelöscht sein (CLD).

·       Assembler-Funktionen im Big-Model müssen als FAR deklariert werden.

·       Bei der Übergabe von FAR-Pointern in z.B. EDX:EAX (s.o.) steht in DX der Selektor und in EAX der Offset.

·       Wenn Parameter über den Stack übergeben werden, und davor EBP auf den Stack gesichert wurde, ist die Startadresse 12 (EBP+12).


Watcom Tips & Infos

(näheres siehe Watcom C/C++ User’s Guide)

 

 

 

·       Inline Assembler mit „#pragma

 

            Dies ist die bessere Methode.

            Beispiel:

            extern  char Funktion(char, char, int,char  *);

            #pragma aux Funktion  = \

                        “start: mov cx,23“\

                        “jmp start“\

                        “mov ebx,888“\

                        parm [al] [ah] [edx] [es esi]\

                        value [al]\

                        modify [cx ebx];

 

            parm:  

            in welche Register sollen die Parameter hinein.

            value: 

            in dem angegebenen Register wird ein Wert zurückgegeben.

            modify:

die hier genannten Register werden von der Inline-Funktion verändert. Die in parm genannten Register müssen hier nicht aufgezählt werden.

            Verwendung von Variablen:

            Die Variablen müssen vor der Inline-Funktion stehen.

 

 

·       Inline Assembler mit „_asm“ oder „__asm

 

            _asm {

                        mov ax,778

            };

 

 

·       32-Bit Modus

 

Im 32-Bit Modus geht es schneller ein DWORD zu laden als ein WORD. Das liegt daran, daß im 32-Bit Modus nicht 66h vor einem DWORD-Ladebefehl steht sondern vor einem WORD-Ladebefehl. Im 16-Bit Modus ist dies genau umgedreht.

 

 

·       String-Makro

           

            #define string(parm) #parm

            string( abc )                  "abc"

            string( "abc" )                "\"abc\""

            string( "abc" "def" )        "\"abc\" \"def\""

            string( \'/ )                     "\\'/"

            string( f(x) )                   "f(x)"

 

 

·       Watcom C Language Reference - Programming Style  (selbst nachschauen)

 

·       Structured Exception Handling (siehe User’s Guide / nur für Windows)

·       32-Bit Pragmas

 

·       Siehe auch User’s Guide - 32-bit Pragmas

 

·       #pragma disable_message ( msg_num {, msg_num} ) [;]        // nur C

·       #pragma enable_message ( msg_num {, msg_num} ) [;]         // nur C

·       #pragma error "error text" [;]

·       #pragma message ( "message text" ) [;]

 

·       #pragma once [;]

     Wenn dieses Pragma in einer Header-Datei steht, wird diese Datei nur einmal geladen.           Das kann helfen um die Zeit des Compilierens zu verkürzen.

 

·       Alias-Pragma:

#pragma aux HIGH_C "*"           \

parm caller []                            \

value no8087                            \

modify [eax ecx edx fs gs];

#pragma aux (HIGH_C) rtn1;

#pragma aux (HIGH_C) rtn2;

 

Mit dem Alias-Pragma kann man ein Attribut auch für andere Funktionen gelten lassen. Die Attribute werden weiter hinten besprochen. Wenn statt “*“ jetzt “_*“ stehen würde, hätten alle Funktionen, die das Attribut von HIGH_C besäßen, einen Unterstrich.

 

·       parm-Attribute: Hiermit wird festgelegt wie Argumente übergeben werden.

·       caller []:                  Argumente von rechts nach links auf den Stack.

·       reverse []:               Argumente von links nach rechts.

·       routine:                  Die aufgerufene Routine nimmt die Argumente vom Stack.

·       caller:                     Die aufgerufene Routine muß nicht den Stack säubern.

·       [eax] [es di]:           Argumente werden in diesen Register übergeben.

 

·       value-Attribute: Hiermit wird festgelegt wie Werte zurückgegeben werden.

·       [ax] ...:                    Wert wird in ax zurückgegeben (Describing Function Return...).

·       caller/routine...:     siehe unter (Describing Function Return Information).

·       aborts:                    Funktion kehrt niemehr zum Aufrufer zurück.

 

·       modify-Attribute: Hiermit wird festgelegt was die Funktion verändert.

·       nomemory:             Die Funktion verändert keine Variablen.

·       exact:                     Nur die Register, die aufgelistet werden, werden verändert.                                  Es wird angenommen, daß die Übergabe-Register unverän-                            dert  bleiben, solange sie nicht aufgelistet sind.

·       [eax ebx]..:             Diese Register werden verändert. Wenn nicht “exact“ davor                                 steht, wird angenommen, daß auch die Übergabe-Register                          (parm - value) verändert werden.

 

 

·       User’s Guide - Watcom C/C++ Compiler Options

 

·       Users’s Guide - The Watcom C/C++ Compilers

 

·       Durch Pragmas kann die normale Übergabe von Werten an externe Assembler-Funktionen geändert werden.

 

 

 

 

 

 

·       DOS/4GW: Speicherzugriff

 

Speicher kann ganz normal mit <malloc> und <free> allociert bzw. deallociert werden. Man kann auch ganz normal darauf zugreifen. Wenn man auf den Low-Memory-Bereich (unter 1MB) zugreifen will muß man folgendes beachten:

 

            FALSCH:

            __segment  screen;

            char __based(void) *scrptr;

            *(screen:>scrptr) = 0x0C;

 

            FALSCH:

            void *scrptr = MK_FP(0xB800,0);

 

            RICHTIG:

            char *screen = (char *) 0xB8000;         // Segment*16

            screen[0] = 0x0C;

 

·       Speicher unter 1MB einfach: Pointer = Segment*16 + Offset.

 

 

·       externer Assembler:

 

            Beispiel:

                        ; im Assembler-Modul

                        .DATA

                        .CODE

                        public Test_

                        Test_ proc far

                             inc eax

                             ret   

                        Test_ endp

                        END

 

                        // im C-Modul

                        extern "C" int __far Test(int);

 

·       Hinter dem Namen der Funktion muß im Assembler-Modul ein Unterstrich stehen.

·       Die Funktion muß immer im Assembler-Modul far sein, und im C-Modul __far.