HSG |
|
Art of Assembly Language Programming and HLA by Randall Hyde
Auch wenn BONSAI-Assembler das Wesentliche an der Maschinenprogrammierung zeigt, so hat es doch seinen eigenen Reiz, einen 'realen' Prozessor zu programmieren. Das 'Herz' der üblichen Rechner mit dem Betriebssystem MS-DOS ist ein Prozessor der Serie 80x86. Den Prozessor kann man mit Hilfe des mit MS-DOS mitgelieferten Programms DEBUG Schritt für Schritt verfolgen und die Speicher- und Registerveränderungen studieren. Dazu muss man auf die Kommandozeilen-Eingabe umschalten (mit Alt-Return erscheint das Fenster im Vollbild).
Nach dem Start meldet sich DEBUG mit einem eigenen Prompt - :
c:\>debug -
Wie in alten DOS-Zeiten erhält man nach Eingabe des Fragezeichens eine Liste der zur Verfügung stehenden Befehle, von denen unten nur die im Folgenden verwendeten aufgeführt werden:
-? assemblieren A [Adresse] ................ { weitere Befehle } anzeigen D [Bereich] { dump } eingeben E Adresse [Liste] { enter } ................ beenden Q { quit } Registeranz. R [Register] ................ verfolgen T [=Adresse] [Wert] { trace } ................
Nach Eingabe von r erhält man die Inhalte der Register angezeigt:
-r AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=2E49 ES=2E49 SS=2E49 CS=2E49 IP=0100 NV UP EI PL NZ NA PO NC 2E49:0100 0000 ADD [BX+SI],AL DS:0000=CD -r ip IP 0100 :0000
Man sieht den Inhalt der vier 16-Bit-Mehrzweckregister AX bis DX, die zur Zeit alle den Wert Null haben. AX dient als Akkumulator bei bestimmten arithmetischen Operationen, BX als Basisregister, CX als Zählregister, DX als Datenregister für Ein- / Ausgabeoperationen. Außerdem werden in der ersten Zeile der Stackpointer SP und drei weitere 16-Bit-Register BP, SI, DI angezeigt. BP dient als Basisregister, SI (source) und DI (destination) dienen als Indexregister für Quell- und Zieloperanden. In der zweiten Zeile folgen die vier Segmentregister DS (Daten), ES (Extra), SS (Stack) und CS (Code). Daneben wird der Inhalt des instruction-pointer IP angegeben. Den Schluß der Zeile bilden die Flags. Alle Zahlangaben sind übrigens 4-stellige Hexadezimalzahlen, dh. CS=2E4916 hat im Dezimalsystem den Wert 2 · 4096 + 14 · 256 + 4 · 16 + 9 · 1 = 11 84910. Dabei haben die hexadezimalen Ziffern A - F die Werte 10 - 15 , also E16 = 1410. Die dritte Zeile zeigt zunächst den Inhalt des Programmzählers, der auf 2E49:0100 steht. Diese Adresse besteht wie die meisten Adressen auf diesen Prozessoren aus Segment (hier Code-Segment CS) und Offset (hier instruction pointer IP). Die physikalisch relevante 20-Bit-Adresse errechnet sich aus Segment · 16 + Offset, hier: 11 849 · 16 + (0 · 163 + 1 · 162 + 0 · 161 + 0 · 1) = 189 840. Auf diese Weise lassen sich mit einem 16-Bit-Prozessor 220 = 1 048 576 = 1MByte Adressen ansprechen. Hinter dieser Adresse steht der Inhalt der Speicherzelle (bzw. der dort beginnenden Speicherzellen bei einem Mehrwort-Befehl), hier: 0000, disassembliert (dekodiert): ADD [BX+SI],AL . Dieser Befehl spricht außer dem Register AL auch eine Speicherzelle an, deren Adresse sich aus Datensegment DS und dem Offset (BX+SI) errechnet. Der Inhalt dieser Speicherzelle wird ganz rechts angezeigt, hier CD16 = 20510 . Gibt man hinter dem Buchstaben r einen Registernamen an, so wird nur dieses Register angezeigt und man hat Gelegenheit, hinter dem Doppelpunkt gegebenenfalls einen neuen Inhalt anzugeben. Auf diese Weise wurde oben das IP-Register auf 0000 gesetzt.
Jetzt soll ein kleines Maschinenprogramm, eingerahmt von nop- (no operation) Befehlen eingeben werden. Der Einfachheit halber wird das Codesegment CS und das Datensegment DS nicht verändert. Von den Registern AX und BX soll nur das niederwertigere (Low-) Byte AL und BL verwendet werden. Die ersten beiden Befehle laden AL und BL mit dem Inhalt der Speicherzellen 0020 und 0021 (jeweils Offsetwerte im aktuellen Datensegment). Dann wird BL auf AL aufaddiert und das Ergebnis nach Speicherzelle 0022 transportiert. Der Befehl a (assemble) gefolgt von einer (Offset-) Adresse gestattet die Eingabe im Assemblercode ab der angegebenen Adresse. Wie immer in DEBUG beendet ein bloßes RETURN den Befehl:
-a 0000 2E49:0000 nop 2E49:0001 nop 2E49:0002 nop 2E49:0003 mov al,[0020] 2E49:0006 mov bl,[0021] 2E49:000A add al,bl 2E49:000C mov [0022],al 2E49:000F nop 2E49:0010 nop 2E49:0011 nop 2E49:0012
Mit e (enter) lassen sich die bewussten Speicherzellen mit Werten füllen, wobei der aktuelle Wert vorher angezeigt wird. In unserem Fall haben alle drei Zellen den Inhalt FF, der mit 3 bzw. 4 überschrieben, bzw. belassen wird:
-e 0020 2E49:0020 FF.03 2E49:0021 FF.04 2E49:0022 FF.
Nach einem letzten Blick auf die Register mit r , wird das Programm ab IP = 0000 mit dem Befehl t (trace) gestartet. Zunächst erscheinen die nop-Befehle und außer einer Erhöhung des IP tut sich nichts. Aber man kann sehen, daß nop durch das Byte 90 kodiert wird:
-r AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=2E49 ES=2E49 SS=2E49 CS=2E49 IP=0000 NV UP EI PL NZ NA PO NC 2E49:0000 90 NOP -t AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=2E49 ES=2E49 SS=2E49 CS=2E49 IP=0001 NV UP EI PL NZ NA PO NC 2E49:0001 90 NOP -t AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=2E49 ES=2E49 SS=2E49 CS=2E49 IP=0002 NV UP EI PL NZ NA PO NC 2E49:0002 90 NOP -t
Mit dem Erscheinen des Befehls MOV AL,[0020] an der Adresse 2E49:0003 wird es spannend. Dieser Befehl, der als nächstes ausgeführt wird, wird den Inhalt der Zelle 0020, der rechts bereits angezeigt wird, nach AL transportieren (move). Ebenso werden auch die anderen Befehle ausgeführt, bitte die Registerinhalte von AX und BX beachten. Nicht vergessen, dass jeder Befehl den IP passend weitersetzen muss, dh. z.B. muss ein 4-Wort-Befehl, wie MOV BL,[0021], den IP um 4 erhöhen:
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=2E49 ES=2E49 SS=2E49 CS=2E49 IP=0003 NV UP EI PL NZ NA PO NC 2E49:0003 A02000 MOV AL,[0020] DS:0020=03 -t AX=0003 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=2E49 ES=2E49 SS=2E49 CS=2E49 IP=0006 NV UP EI PL NZ NA PO NC 2E49:0006 8A1E2100 MOV BL,[0021] DS:0021=04 -t AX=0003 BX=0004 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=2E49 ES=2E49 SS=2E49 CS=2E49 IP=000A NV UP EI PL NZ NA PO NC 2E49:000A 00D8 ADD AL,BL -t AX=0007 BX=0004 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=2E49 ES=2E49 SS=2E49 CS=2E49 IP=000C NV UP EI PL NZ NA PO NC 2E49:000C A22200 MOV [0022],AL DS:0022=FF -t AX=0007 BX=0004 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=2E49 ES=2E49 SS=2E49 CS=2E49 IP=000F NV UP EI PL NZ NA PO NC 2E49:000F 90 NOP
Nach Ausführung des Programms kann man sich Programmcode in Maschinensprache und die Daten mit dem Befehl d (engl. dump= hinplumpsen lassen) ansehen. In der Maschine wird der Binärcode verwendet, der sich besonders günstig hexadezimal abkürzen läßt, z.B B216 = 1011 00102 .
Ein Dump zeigt in jeder Zeile Anfangsadresse, Inhalt von 16 Speicherzellen, erst hexadezimal kodiert, dann in ASCII-Zeichen, wobei nicht druckbare Zeichen oder Zeichen mit einer Nummer über 127 durch einen Punkt . dargestellt werden. Beispiel: 3.Zeile, vorletzes Zeichen 4E entspricht N .
-d 0000 2E49:0000 90 90 90 A0 20 00 8A 1E-21 00 00 D8 A2 22 00 90 .... ...!....".. 2E49:0010 90 90 17 03 86 28 B2 03-01 01 01 00 02 FF FF FF .....(.......... 2E49:0020 03 04 07 FF FF FF FF FF-FF FF FF FF 67 28 4E 01 ............g(N.
Der Befehl q (quit) beendet die kleine DEBUG-Sitzung:
-q C:\>
Viel ausführlichere Informationen (z.B. über "richtige" Maschinenprogrammierung in Turbo-Pascal mit INLINE-Code, Anmerkung: In Delphi kann man direkt Assemblercode verwenden.) erhält man aus Metzler Informatik, Grundband, 2.Auflage in dem Kapitel "Hardwarenahe Programmierung", S.298 ff. Wobei das dort Gebotene weit über das hinausgeht, was unserer Meinung nach in allgemeinbildenden Schulen erforderlich ist. Ebenso scheint der dort erwähnte Mini-Assembler MINASS entbehrlich, siehe DEBUG. Nichtsdestotrotz klar geschrieben und interessant zu lesen. Sehr schön zusammengefasst wird der 8086 und seine Befehle im DUDEN Informatik, S.385ff dargestellt.