Home

Sitemap

8086 Assembler:
Assemblerauswahl

Dies ist eine Sammlung von älteren Assemblerunterlagen zur MS-DOS-Programmierung, die vielleicht dem Einen oder Anderen nützlich sein könnte. Ursprünglich waren die Programmierungsbeispiele für den Microsoft Macro Assembler Version 4.00 codiert. Diese Version 4.00 stammt von 1985 und erzeugt 16-bit-Code. Bis auf eine Ausnahme sind sie auf den moderneren Microsoft Assembler ML.EXE Version 6.14 angepasst worden.

Ein wenig über die erforderlichen Anpassungen steht auf der Seite „Assemblieren und Linken für .COM-Dateien beim MASM 4.00 und beim ML - Assembler“.

Die erzeugten MS-DOS-Programme sind sehr kurz. Das fertige Beispielprogramm (frage.com) ist gerade mal 216 Bytes lang!

Die Verfahren funktionieren unter Windows 98, Windows XP und Windows VISTA. Ab Windows 7 ist es mit der Assemblerprogrammierung, wie sie auf diesen Webseiten beschrieben ist, vorbei.

Microsoft liefert bei VISTA einen deutschen 16-Bit-Tastaturtreiber mit. Um ihn zu nutzen, muss man in der Datei
%SystemRoot%\system32\autoexec.nt eintragen:
LH KB16 GR,,%SystemRoot%\system32\keyboard.sys

4DOS ist eine komfortable Alternative zu COMMAND.COM und CMD.EXE. Allerdings werden von 4DOS unter XP, VISTA und wohl auch Windows 7 die langen Dateinamen im MS-DOS-Stil angezeigt. 4DOS 7.50 und sein Nachfolger 4dos 8.00 sind Freeware und lassen sich aus dem Internet herabladen.

Vom gleichen Hersteller wie 4DOS kommt „Take Command®“. Dessen Freeware-Version Take Command/LE bietet den Benutzungskomfort von 4DOS und kommt mit langen Dateinamen zurecht. Das Program ist für Windows XP und seine Nachfolger gechrieben.


8086 Assembler, Adressbildung


Zurück zur Assemblerauswahl

ADRESSE (Adressbildung)

Segmentanteil * 16d + Offsetadresse = physikalische Adresse
[16 bit]*16d->[20 bit]   [16 bit]       [20 bit]

Segmentanteil *16        1011 0000 1111 1100
+ Offsetanteil                0000 0001 1100 1101
-------------------------------------------------
= physikalische Adresse  1011 0001 0001 1000 1101
Standardmäßige Segmentzuordnung: (EA=effektive Adresse)
Zugriffsart Standardsegment Alternativen Offset
Befehl lesen CS keine IP
Datenoperation DS CS,ES,SS EA
Stapeloperation SS keine SP
BP als Basis SS CS,DS,ES EA
BX als Basis DS CS,ES,SS EA

Segmentregister

Der 8086 und der 8088 verwenden 20-bit-Adressen für die Speicheradressierung. Somit können sie bis zu 1 Megabyte Speicher adressieren. Die Register und die Adressfelder aller Instruktionen sind jedoch nur 16 bit lang.

Immer, wenn auf den Speicher zugriffen wird, liefert das Programm eine 16 bit lange Adresse. Weitere 16 bit der Adressbildung werden aus dem Segmentregister bezogen. Diese werden um 4 Bits (1 Halbbyte bzw. 1 Nibble) nach links verschoben und auf die 16 Bits Adressanteil des Programmes aufaddiert, um die wirkliche Speicheradresse zu bilden. Der Inhalt der Segmentregister kann verändert werden, so dass ein Zugriff auf jede Speicheradresse möglich ist. Die Segementregister sind Spezialregister. Es gibt je eines für

  • den Codezugriff
  • die meisten Datenzugriffe
  • den Stackzugriff
  • für zusätzliche Datenzugriffe.

Man tendiert dazu, den Inhalt der Segmentregister am Beginn des Programmes mit Daten zu versorgen und sie im Programmablauf möglichst nicht zu verändern.

    MOV  AX,DATASEG
    MOV  DS,AX         ;Set value of Data segment
    ASSUME DS:DATASEG  ;Tell assembler DS is usable
    .......
    MOV  AX,PLACE      ;Access storage symbolically by 16 bit address

Im vorstehenden Programmbeispiel weiß der Assembler, dass keine Besonderheiten vorliegen, da die Maschine von sich aus das DS-Register zur Adressbildung für den normalen Zugriff auf Daten verwendet.

Falls man im obigen Beispiel ES statt DS für den Datenzugriff verwenden möchte, müsste man es gesondert angeben. Vor der MOV-Anweisung, die auf die symbolische Adresse SPACE zugreift, würde eine vorgestelltes ES der Maschine mitteilen, dass hier ES anstelle von DS zur Adressbildung zu verwenden ist.

Einige Konventionen erleichtern den Umgang mit den Segmentregistern. Handelt es sich beispielsweise um eine COM-Programm (und nicht um eine EXE-Programm), haben alle vier Segmentregister den gleichen Inhalt. Das Programm läuft vollständig in einem Bereich von 64 kB ab. Wenn erforderlich, kann man diesen Adressraum verlassen, muss es aber nicht.

Adressierungsarten

Direkte Registeradressierung ADD AX,CX
JMP AX
Verwendet die direkten Inhalte der Register
Indirekte Speicheradressierung ADD AX,[BX]
ADD [BX],AX

JMP [CX]

Verwendet den Inhalt von BP, BX, DI oder SI um den Offset der Speicheradresse zu bestimmen.

Bei einigen Befehlen sind alle allgemeine Register verwendbar.

Unmittelbare Adressierung ADD AX,123 Verwendet unmittelbar den Wert des 2. Argumentes.
Relative Speicheradressierung ADD AX,[123] Das 2.Argument ist der Offset der Speicheradresse.
Basisadressierung,
basisrelative Adressierung
MOV FELD[BX],AL
MOV AX,[BX]+6
1. Beispiel: die Adresse ist die Summe aus der Offsetadresse FELD plus Inhalt von BX.
2. Beispiel: die Adresse entsteht aus dem Inhalt von BX plus dem Wert 6.
Beide Beispiel: Anstelle von BX kann auch BP verwendet werden.
Indizierte Adressierung,
direktindizierte Adressierung
MOV FELD[DI],AL wie Basisadressierung,nur DI und SI als Indexregister.
Basisindizierte Adr. MOV AX,[BX][SI]100 Die Adresse ist die Summe aus den Inhalten von
BX oder BP +
DI oder SI +
einer Offsetadresse.

Annahmen bei Auslassung von Angaben zur Verwendung der Segment-Register (default rules for which segment registers are used) zur Adressbildung:

  1. CS wird verwendet zur Adressbildung von Sprungzielen im Bereich NEAR DIRECT.

    Bei einer Adressbildung für NEAR INDIRECT wird DS verwendet, um die Adresse aus dem Speicher zu lesen. Anschließen wird CS zur Adressvervollständigung verwendet. Für Verzweigungen im FAR-Bereich wird CS selbst geändert.

    Der Adresszeiger (instruction counter) zeigt implizit immer auf das Codesegment.

  2. SS wird zur Vervollständigung einer Adresse verwendet, wenn BP einbezogen ist. Andernfalls wird DS zur Vervollständigung einer Datenadresse verwendet.

  3. Bei Zeichenkettenbefehlen (string instructions) wird das Ziel immer aus ES und DI gebildet. Die Quelle wird normalerweise aus DS und SI gebildet. Wenn ein Segment-Präfix angegeben ist, überschreibt er die Quelle, nicht das Ziel.

Namenskonventionen
für die Quellprogramme


Zurück zur Assemblerauswahl
Ein Name ist eine Abfolge von Zeichen bestehend aus Buchstaben, Ziffern und den folgenden Sonderzeichen: Unterstrich (_), Prozentzeichen (%), Fragezeichen (?), Dollarzeichen ($), dem at-Zeichen bzw. Klammeraffen (@) und dem Punkt (.). Folgende Regeln gelten:
  • Ein Name darf nicht mit einer Ziffer beginnen. Ziffern dürfen jedoch an anderen Stellen des Namens enthalten sein. (sym1 ist ein zulässiger Name, 1sym ist ein nicht zulässiger Name).
  • Ein Name darf mit einem Punkt beginnen. Ein Punkt an anderer Stelle ist nicht zulässig. (.sym1 ist ein zulässiger Name, sym.1 ist ein nicht zulässiger Name).
  • Ein Name darf nicht einem reservierten Wort oder einem reservierten Symbol entsprechen. Beispielsweise sind nicht zulässige Namen: AX, ?, $, mov und PAGE.

Registerbeschreibung


Zurück zur Assemblerauswahl
Datenregister Zeiger- und
Indexregister
EAX32 Erweitertes AX ESP32 Erweiterter SP
AX16 Akkumulator SP16 Stackpointer
AH8 AL8   EBP32 Erweiterter BP
EBX32 Erweitertes BX BP16 Basepointer
BX16 Base
Adressierung
ESI32 Erweiterter SI
BH8 BL8   SI16 Sourceindex
ECX32 Erweitertes CX EDI32 Erweiterter DI
CX16 Counter
(Schleifen)
DI16 Destinationindex
CH8 CL8  
EDX32 Erweitertes DX
DX16 Data Segmentregister
DH8 DL8 (Portadressierung) CS16 Codesegment
Kontrollregister DS16 Datasegment
EIP32 Erweiterter IP SS16 Stacksegment
IP16 Instruction Pointer ES16 Extrasegment
Eflags32 Erweitertes PSW FS16 Extrasegment
Flags Process Status Word GS16 Extrasegment

Die Register mit farblich hervorgehobenen Hintergrund wurden mit dem 80386 eingeführt. Erst der 80386 und seine Nachfolger beinhalten diese Register. Bei EAX, EBC, ECX, EDX, EIP und Eflags handelt es sich um Erweiterungen der Register AX, BX, CX, DX, IP und Flags auf 32 Bit. Bei den Segmentregistern FS und GS handelt es sich um neu hinzugekommene weitere Segmentregister.


Process Status Word beim 8086
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
        OF DF IF TF SF ZF - AF - PF - CF
Process Status Word beim 80286
    NT I/O OF DF IF TF SF ZF - AF - PF - CF
CF Carry Flag Übertragskennzeichen (Statusflag)
Wird auch als Borrow bezeichnet.
PF Parity Flag Paritätskennzeichen (Statusflag)
1=gerade Anzahl 1er-bits im Ergebnis.
AF Auxiliary Flag Hilfsübertragungskennzeichen (Statusflag)
für AL, bezieht sich auf den Übertrag vom niederwertigen Halbbyte.
ZF Zero Flag Nullkennzeichen (Statusflag)
SF Sign Flag Vorzeichenbit (Statusflag)
1=Ergebnis der Integerberechnung ist negativ.
TF Trap Flag Einzelschrittkennzeichen (Steuerungsflag)
für Fehlersuche (Debugging).
IF Interrupt enable Flag Unterbrechungsfreigabekennzeichen (Steuerungflag)
1=Interrupt enabled
DF Direction Flag Richtungskennzeichen (Steuerungsflag)
1=Stringbefehl arbeitet von hoher Adresse zur niedrigen.
0=Stringbefehl arbeitet von niedriger Adresse zur hohen.
OF Overflow Flag Überlaufkennzeichen (Statusflag)
für vorzeichenbehaftete Operationen.
1=es trat Überlauf (Carry) oder Ausleihen (Borrow) auf. Das Ergebnis ist zu groß oder zu klein.
80286 im Protected Mode:
I/O I/O Privilege Level Maximale Zugriffsrechte für E/A Operationen
NT Nested Task Kennzeichen für verschachtelte Tasks

Allgemeine Register

AX und DX sind Register für allgemeine Aufgaben. Sie werden zu Spezialregistern, wenn es um den Zugriff auf Maschinen- und Systemschnittstellen geht (BIOS-Funktionen und INT 21h- Aufrufe). Das AX-Register ist besonders für arithmetische Aufgaben geeignet, denn einige der arithmetischen Operationen des Befehlssatzes laufen ausschließlich über das AX-Register. AX bzw. AL hat deshalb den Spitznamen "Akku" erhalten. Weiterhin laufen die Daten der Ein-/Ausgabeoperationen IN und OUT über das AX- bzw. AL-Register.

BX ist ein Register für allgemeine Aufgaben. Es hat eine Sonderfunktion bei der Adressierung.

CX ist ein Register für allgemeine Aufgaben. Es hat bei einigen Befehlen die Sonderfunktion des Zählers (z.B. als Schleifen- oder Wiederholungszähler).

DX ist ein Register für allgemeine Aufgaben. Es verlängert bei einige arithmetischen Operartionen das AX-Register. Bei den Ein-/Ausgabeoperationen IN und OUT kann es zur Angabe der Portadressen verwendet werden.

AX, BX, CX und DX können in die Hälften AH, AL, BH, BL, CH, CL, DH und DL unterteilt und als solche verwendet werden.

SI und DI sind nur als 16-bit-Register verwendbar . Sie können (wie auch BX) zur Indexadressierung genutzt werden. Hauptsächlich werden sie als Stringpointer (Zeiger auf Zeichenketten) genutzt.

SP dient als Zeiger auf den Stack.

BP ist manipulierbar, und somit ein Verwandter von SP. Er hilft, auf Daten zuzugreifen, die im Stack abgelegt sind.

Die meisten 16-bit-Befehle können auf die Register SI, DI, SP oder BP angewendet werden.

8086 Assembler: Ablaufsteuerung


Zurück zur Assemblerauswahl
CALL, RET, INT, IRET, JMP
Conditional jump
Schleifen

Hier geht es um

  1. CALL, RET -- Call und Return
  2. INT, IRET -- Interrupt und Return-from-Interrupt
  3. JMP -- Sprung oder "Verzweigung"
  4. Verschiedene Befehle für den Sprung in Abhängigkeit von Bedingungen (Bedingter Sprung oder "conditional jump".
  5. LOOP, LOOPE, LOOPNE, LOOPNZ, LOOPZ -- verschiedene Befehle welche die Programmierung von Zählschleifen erleichtern.

CALL, RET, INT, IRET, JMP

Achtung, CALL und INT schieben Adressinformationen auf den Stack. RET und IRET entfernen diese Informationen wieder vom Stack.

Um System-Funktionen aufzurufen, wird der INT-Befehl benutzt. Man muss nicht wissen, wie dies genau funktioniert, sondern man kann hier "kochbuchartig" vorgehen.

Die Übergabe von Steuerungsanweisungen (CALL, RET, JMP) erfordert ein gewisses Verständnis. Sie können so klassifiziert werden:

  1. alle können entweder NEAR (das Register CS bleibt unverändert) oder FAR (das Register CS ändert seinen Inhalt) sein
  2. JMPs und CALLs können DIRECT sein (das Ziel der Verzweigung ist in dem Maschinencode des Befehls unmittelbar enthalten) oder INDIRECT (das Ziel wird von einer anderen Speicherstelle oder einem Register zur Verfügung gestellt. Dadurch kann das Ziel während des Programmablaufes errechnet werden)
  3. wenn NEAR und DIRECT, dann kann JMP als SHORT (weniger als 128 bytes entfernt) or LONG codiert werden

Bedingter Sprung (Conditional jump)

Die verschiedenen Sprungbedingungen sind eher verwirrend. Hier eine Übersicht:
JZ    bedeutet was es sagt: verzweige bei gesetztem Zero-bit
JNZ   bedeutet was es sagt: verzweige, wenn das Zero-bit nicht gesetzt ist
JG    größer: bedeutet "wenn die vorzeichenbehaftete
      Differenz positiv ist"
JA    über (above): bedeutet "wenn die nicht vorzeichenbehaftete
      Differenz positiv ist"
JL    kleiner (less): bedeutet "wenn die vorzeichenbehaftete
      Differenz negativ ist"
JB    unter (below): bedeutet "wenn die nicht vorzeichenbehaftete
      Differenz negativ ist"
JC    carry: ist das Gleiche wie JB
JP    verzweige bei gesetztem Paritätsflag (even)
JPE   parity even: ist das Gleiche wie JP
JNP   verzweige bei nicht gesetztem Paritätsflag (odd)
JPO   parity odd: ist das Gleiche wie JNP

JCXZ  verzweige wenn CX den Inhalt 0 hat.
JECXZ verzweige wenn ECX den Inhalt 0 hat. Der Befehl wurde mit
      dem 386er Prozessor eingeführt.

Für .COM-Programme bis einschließlich 80286er Prozessor gilt, dass alle anwendbaren bedingten Verzweigungen automatisch DIRECT, NEAR und SHORT sind. Das bedeutet: Man kann nicht weiter als 128 Bytes entfernt vor oder zurück verzweigen. Ab dem 80386er Prozessor entfällt die genannte Entfernungsbeschränkung. Für „Verzweige wenn“ Jcc kann „Jcc“ sein:

Ohne Vorzeichen (unsigned):
(Zu den Flagbezeichnungen wie CF, OF, PF, SF, ZF  i siehe hier).

JA       größer als              CF=0 und ZF=0    (wie JNBE)
JAE      größer oder gleich      CF=0             (wie JNB und JNC)
JB       kleiner als             CF=1             (wie JNAE und JC)
JBE      kleiner als oder gleich CF=1 und ZF=1    (wie JNA)
JC       Carry Flag gesetzt      CF=1

JNA      nicht größer als        CF=1 und ZF=1    (wie JBE)
JNAE     nicht größer/gleich     CF=1             (wie JB und JC)
JNB      nicht kleiner           CF=0             (wie JAE und JNC)
JNBE     nicht drunter/gleich    CF=0 und ZF=0    (wie JA)
JNC      kein Übertrag           CF=0

Mit Vorzeichen (signed):
JG       größer                  ZF=0 und SF=OF   (wie JNLE)
JGE      größer/gleich           SF=OF            (wie JNL)
JL       kleiner                 SF<>OF           (wie JNGE)
JLE      kleiner/gleich          ZF=1 und SF<>OF  (wie JNG)

JNG      nicht größer            ZF=1 oder SF<>OF (wie JLE)
JNGE     nicht größer/gleich     SF<>OF           (wie JL)
JNL      nicht kleiner           SF=OF            (wie JGE)
JNLE     nicht kleiner/gleich    ZF=0 und SF=0    (wie JG)

JNO      kein Überlauf           OF=0
JNS      positv                  SF=0
JO       Überlauf                OF=1
JS       negativ                 SF=1

Sonstige:
JCXZ     CX-Register gleich 0
JE       gleich                  ZF=1             (wie JZ)
JNE      nicht gleich            ZF=0             (wie JNZ)
JNZ      nicht null              ZF=0

JNP      keine Parität           PF=0             (wie JPO)
JP       Parität                 PF=1             (wie JPE)
JPE      gerade Parität          PF=1             (wie JP)
JPO      ungerade Parität        PF=0             (wie JNP)

JZ       null                    ZF=0             (wie JE)

Schleifen

Hierzu gehören LOOP, LOOPE, LOOPNE, LOOPNZ, LOOPZ.

allgemeine Syntax:
LOOPcc  <Sprungziel>
Bei jedem Durchgang wird das CX-Register um 1 verringert. Danach erfolgt eine SHORT-Verzweigung zum angegebenen Sprungziel, oder der nächstfolgende Befehl wird ausgeführt. LOOPcc setzt keine Flags.

cc siehe bei Jcc. Die Bedingung bezieht sich auf die Dekrementierung von CX.

Verwendungen:
LOOP für Schleifen.
LOOPE und LOOPZ, um in Schleifen nach Werten ungleich 0 zu suchen.
LOOPNE und LOOPNZ, um in Schleifen nach Werten gleich 0 zu suchen.

8086 Assembler,
Sprungverteiler (berechneter Sprung)


Zurück zur Assemblerauswahl
Eine typische Verwendung zeigt der nachfolgende Ausschnitt: Beim Aufruf des Programmes wird ein Parameter mitgegeben. In Abhängigkeit vom mitgegebenen Parameter verzweigt das Programm. Maßgebend für das Verzweigungsziel ist das erste (häufig auch das einzige) Zeichen des Parameters.
BATch Control Program Vers.:29.10.95

batc ?     Eingabe J/N im Dialog
           Errorlevel bei Ausgang: 0 nach J od.Y, 1 nach N, 3 nach ^C
batc W     prompted mit <-'
batc B     Warmboot
batc Cx    setzt Randfarben (EGA/VGA) x=0...F, vergl. batc F
batc E...  Ausgabe ESC... zum Bildschirm (z.B. für ANSI-Treiber)
batc F     Anzeige Farbpalette auf Bildschirm.
batc J     setzt Errorlevel auf 0
batc K     Keycode anzeigen
batc M     Verwendung als more-Filter, Aufruf hinter |
batc N     setzt Errorlevel auf 1
batc P...  Textausgabe auf Drucker LPT1. Anleitung: batc P
batc R     Resetbefehl fuer Drucker an LPT1 (BIOS)
batc S     Zeichenvorrat zum Bildschirm
batc V     Zeigt den Inhalt des Video-State-Buffers
batc xx    VGA auf xx Zeilen/Seite, 25 / 28 / 43
batc Y     Anzeige diverser Systeminformationen

Die dafür zuständige Programmierung wird nachfolgend gezeigt. Mit den ersten vier Befehlen

mov        al,byte ptr cs:fcb+1
lea        di,btab   ;befehlstabelle
mov        cx,lbtab  ;deren laenge
repne      scasb

wird festgestellt, ob der erste Buchstabe des beim Programmaufruf mitgegebenen Parameters in der Tabelle 'MYWV42BCDEFPRS?NJK ' enthalten ist. Ist er enthalten, erfolgt eine Verzweigung zur Marke w1, andernfalls geht es zur Marke hilfe- dort wird die oben dargestellte kurze Bedienungsanleitung am Bildschirm gezeigt.

Ganz wichtig: Das Registerpaar CX enthält die Position des übereinstimmenden Zeichens in der Tabelle, und zwar von hinten gezählt. Ab dem Merkmal wohin beginnt eine Verzweigungsadresstabelle. Sie enthält alle in Frage kommenden Zieladressen, ist jedoch genau andersherum angeordnet wie die Tabelle btab der möglichen Aufrufparameter.

Beim Merkmal w1 erhält das DI-Registerpaar die Adresse des Sprungzielen und der Sprung wird ausgeführt.

Das verwendeten Unterprogramm prmsg stellt den nachfolgenden Text auf dem Bildschirm dar. Das Macro dbl definiert eine Textzeile mit abschließendem Zeilenwechsel.

        mov        al,byte ptr cs:fcb+1
        lea        di,btab   ;befehlstabelle
        mov        cx,lbtab  ;deren laenge
        repne      scasb
        je         w1
        jmp        hilfe          ;ungueltiger aufrufparameter


btab    db        'MYWV42BCDEFPRS?NJK ';mgl.Parameter
lbtab   equ       $-btab
wohin   dw        hilfe        ;hilfeanzeige
        dw        keycode      ;tastencode zeigen
        dw        ret0         ;errorlevel auf 0
        dw        ret1         ;errorlevel auf 1
        dw        inkjn        ;eingabe j/n
        dw        chrshow      ;zeichensatz
        dw        resetp       ;reset printer
        dw        list_p       ;druckausgabe
        dw        farbe        ;farbpalette zum bildschirm
        dw        scrn_esc     ;bildchirmausgabe esc-string
        dw        datetest     ;datumtest und Blockcursor ein
        dw        crand        ;setzt farbigen rand (ega)
        dw        boot         ;warmboot
        dw        ega25        ;vga auf 25 / 28 Zeilen / Seite
        dw        ega43        ;vga auf 43 Zeilen/Seite
        dw        vgainfo      ;anzeige video state buffer
        dw        weiter       ;Abwarten Eingabe der CR-Taste
        dw        pcstat       ;PC-Status anzeigen
        dw        more         ;Verwendung als More-Filter
w1:     add       cx,cx        ;*2->tabellenoffset
        mov       di,cx
        jmp       wohin[di]

;hilfstextanzeige wenn aufruf ohne parameter

hilfe:
        call        prmsg
 dbl   'BATch Control Program Vers.:29.10.95'
 dbl
 dbl   'batc ?     Eingabe J/N im Dialog'

Letztes Update: 21.12.2013