Die Assemblerprogrammierung der Prozessoren 80 ×86/Pentium

Werbung
Die Assemblerprogrammierung
der Prozessoren 80 ×86/Pentium
Bernd Dumbacher
18. Juni 2004
Zusammenfassung
Dieses Manuskript startet mit einem einfachen Assemblerprogramm,
das in den Rechnerübungen bearbeitet und modifiziert wird. Mit ihm
soll der Einstieg in die - für Hochsprachenprogrammierer ganz andere Welt der Assemblerprogrammierung gewagt werden. Die in diesem Programmbeispiel angeschnittenen Themen wie Speicherverwendung, Adressierung, Datendefinitionen, Assemblerbefehle und Struktur von Assemblerprogrammen werden in den folgenden Abschnitten ausführlich und
systematisch behandelt. Danach folgen weiterführende Aspekte wie Programmsteuerung, Prozeduren, Dateienhandhabung, Bitmanipulation und
Stringverarbeitung. Schließlich wird auf die Verbindung von Assemblerund Hochsprachenprogrammen, die Programmierung des mathematischen
Koprozessors und die Steuerung von peripheren Geräten über I/O-Ports
eingegangen. Der Abschnitt über die prozessorabhängigen Teile des Assemblers ist direkt aus [1] übernommen.
1
Inhaltsverzeichnis
1 Einleitung
5
2 Erstes Programmbeispiel
6
3 Primärer Speicher
3.1 Speichertechnologien . . . . . . . . . . . . . . .
3.2 Register . . . . . . . . . . . . . . . . . . . . . .
3.3 Arbeitsspeicher . . . . . . . . . . . . . . . . . .
3.3.1 Schreiben und Lesen im Arbeitsspeicher,
3.3.2 Schutzmaßnahmen im Arbeitsspeicher .
3.4 Stack . . . . . . . . . . . . . . . . . . . . . . . .
3.4.1 Initialisierung des Stacks . . . . . . . . .
3.4.2 Benutzung des Stack . . . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
little endian
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
.
.
.
.
.
.
.
.
8
8
9
10
11
11
12
12
13
4 Erstellung eines Programms
4.1 Einführung . . . . . . . . . . . . . . . . . . .
4.2 Grundstruktur eines Assemblerprogramms .
4.3 Erstellung eines lauffähigen Programmmoduls
4.3.1 Ablaufdiagramm . . . . . . . . . . . .
4.3.2 Editieren . . . . . . . . . . . . . . . .
4.3.3 Assemblieren . . . . . . . . . . . . . .
4.3.4 Linken . . . . . . . . . . . . . . . . . .
4.4 Werkzeuge zur Programmentwicklung . . . .
4.4.1 Der GNU-Debugger . . . . . . . . . .
4.4.2 Spezielle Werkzeuge . . . . . . . . . .
4.5 Assembleranweisungen . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
15
15
15
16
16
16
16
17
18
18
19
20
5 Adressierung
5.1 Direkte Adressierung . . . . . . . .
5.2 Indirekte Adressierung . . . . . . .
5.3 Indizierte Adressierung . . . . . . .
5.4 Parameterübergabe in Prozeduren
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
22
22
23
23
25
6 Datendefinitionen
6.1 Adressierung der Datenblöcke . . . . . . . . . . . .
6.2 Strukturierung der Datenblöcke . . . . . . . . . . .
6.3 Arithmetik für Adressen, Konstante und Festwerte
6.4 Konflikte bei Datenformaten . . . . . . . . . . . .
6.4.1 Operandengrößen-Zusatz (OpSize-Suffix) .
6.4.2 Direkte Umwandlung (Erweiterung) . . . .
6.4.3 Erweiterndes Kopieren . . . . . . . . . . .
6.5 Direktiven . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
28
28
29
30
30
30
30
31
31
7 Allgemeine Befehle
7.1 Datenmanipulation . . . . . . . . .
7.2 Arithmetikbefehle . . . . . . . . .
7.2.1 Arithmetik-Flaggen . . . .
7.2.2 Addition, Subtraktion . . .
7.2.3 Multiplikation und Division
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33
33
34
34
35
36
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8 Programmsteuerung
8.1 Labels und flag-Register . . . . . . . . . . . . . . . . . . .
8.2 Sprünge . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.2.1 Unbedingter Sprung . . . . . . . . . . . . . . . . .
8.2.2 Bedingter Sprung . . . . . . . . . . . . . . . . . . .
8.3 Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.3.1 Fuß- und kopfgesteuerte Schleifen, Verzweigungen
8.3.2 loop-Schleifen . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
39
39
39
40
40
42
42
43
9 Prozeduren
9.1 Prozedur und Programm . . . . .
9.2 Aufbau einer Prozedur . . . . . .
9.3 Aufruf . . . . . . . . . . . . . . .
9.4 Parameterübergabe . . . . . . . .
9.4.1 Grundsätzliches . . . . . .
9.4.2 Register . . . . . . . . . .
9.4.3 Stack . . . . . . . . . . .
9.5 Lokale Variable . . . . . . . . . .
9.6 Rücksprung . . . . . . . . . . . .
9.7 Beispiel . . . . . . . . . . . . . .
9.8 Makros . . . . . . . . . . . . . .
9.9 Vergleich Prozedur/Macro . . . .
9.10 Bibliotheken . . . . . . . . . . . .
9.10.1 Prozeduren . . . . . . . .
9.10.2 Makros . . . . . . . . . .
9.11 Programmaufruf mit Parametern
9.12 Beispiel . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
45
45
46
47
48
48
48
49
49
49
49
50
51
52
52
52
52
53
10 Handhabung von Dateien
10.1 Interruptverarbeitung .
10.2 Zugriff auf Dateien . . .
10.3 Beispiele . . . . . . . . .
10.4 Fehlerbehandlung . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
56
56
57
57
59
11 Bitmanipulation
11.1 Logische Verknüpfungen . . . . . . . . . . . . .
11.2 Testfunktionen . . . . . . . . . . . . . . . . . .
11.3 Schiebeoperationen . . . . . . . . . . . . . . . .
11.3.1 Überblick . . . . . . . . . . . . . . . . .
11.3.2 Shift Left- und Shift Right-Operationen
11.3.3 Shift Arithmetic Left/Right (sal, sar) .
11.3.4 Rotate-Befehle (rol, ror, rcl, rcr) . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
60
60
61
62
62
62
63
63
12 Stringverarbeitung
12.1 Einfache Stringbefehle . . . . . . . . . . . . . . . . . . . . . . .
12.2 Wiederholungsbefehle . . . . . . . . . . . . . . . . . . . . . . .
12.3 Übersetzungsbefehl xlat . . . . . . . . . . . . . . . . . . . . . .
64
64
64
66
.
.
.
.
.
.
.
.
.
.
.
.
3
.
.
.
.
.
.
.
.
13 Hochsprachenprogramme
13.1 Inline-Assemblerbefehle . . . . . . . . . . . . . . . . . . . . . .
13.2 Verbindung von Assembler- und Hochsprachenmodulen . . . .
13.3 GNU-Konventionen . . . . . . . . . . . . . . . . . . . . . . . . .
67
67
70
71
14 Programmierung des Koprozessors
14.1 Ganze Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14.2 Gleitkommazahlen . . . . . . . . . . . . . . . . . . . . . . . . .
14.2.1 Mantisse und Exponent . . . . . . . . . . . . . . . . . .
14.2.2 Lage des Dezimalpunktes . . . . . . . . . . . . . . . . .
14.2.3 Darstellung im Koprozessor 80 ×87 . . . . . . . . . . .
14.3 Die Architektur des 80 ×87-Koprozessors . . . . . . . . . . . .
14.4 Befehle, Formate . . . . . . . . . . . . . . . . . . . . . . . . . .
14.5 Koordinierung des Speicherzugriffes von CPU und Koprozessor
14.6 Erzeugung von Gleitkommazahlen . . . . . . . . . . . . . . . .
72
72
72
72
72
72
74
74
77
78
15 Input/Output Ports
15.1 Adressraum der I/O Ports . . . . . . . . . . . . . . . . . . . . .
15.2 Adressierung der I/O Ports . . . . . . . . . . . . . . . . . . . .
15.3 Schutzmechanismen beim Zugriff auf die I/O Ports . . . . . . .
15.4 Beispiel: Tonausgabe mit dem Systemlautsprecher über I/O Ports
15.5 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . .
79
79
80
80
81
84
16 80 386 Dependent Features
16.1 AT&T Syntax versus Intel Syntax
16.2 Opcode Naming . . . . . . . . . . .
16.3 Register Naming . . . . . . . . . .
16.4 Opcode Prefixes . . . . . . . . . .
16.5 Memory References . . . . . . . . .
16.6 Handling of Jump Instructions . .
16.7 Floating Point . . . . . . . . . . .
16.8 Writing 16-bit Code . . . . . . . .
16.9 Notes . . . . . . . . . . . . . . . .
85
85
85
86
86
87
88
88
89
89
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
17 Direktiven
91
18 Systemfunktionen
95
4
1
Einleitung
Vorrangiges Ziel der Lehrveranstaltung maschinenorientrierte Programmierung
ist die Fähigkeit zur selbständigen Erstellung von Assemblerprogrammen. Diesem Ziel sollen die Vorlesung und die praktischen Übungen am Rechner dienen.
In der Vorlesung wird ein vertiefter Einblick in die Arbeitsweise des verwendeten Rechnersystems geboten. Damit soll das Arbeiten am Rechner vorbereitet
und intensiviert werden. Da von Anfang an der praktischen Arbeit am Rechner
oberste Priorität gilt, muss im Zweifelsfall die systematische Vermittlung des
Lehrstoffs hinter den Bedürfnissen der Programmierübungen zurückstehen. Die
Lehrinhalte werden in erster Linie in der zeitlichen Folge vermittelt, die durch
die Programmierübungen am Rechner gefordert wird.
Die praktischen Übungen finden an Linux-Rechnern statt und benutzen den
GNU-Assembler, der in der Linux-Distribution enthalten ist.
Für die Lehrveranstaltung maschinenorientierte Programmierung (Assemblerprogrammierung) werden Kenntnisse von Grundlagen der Informatik vorausgesetzt. Diese beinhalten Kenntnisse des Aufbaus und der Arbeitsweise des
Rechners (von-Neumann-Rechner), insbesondere des Zusammenwirkens von
Prozessor und Speicher und der Informationsdarstellung im Rechner. Diese
sind z. B. bei [20] und der dort genannten Literatur zu finden. Daneben werden Grundkenntnisse des Betriebssystems Linux, insbesondere der wichtigsten
Kommandos und Gewandheit im Umgang mit einem Texteditor, möglichst dem
Editor emacs, erwartet.
5
2
Erstes Programmbeispiel
.text
start:
MSG:
LMSG:
.globl
movl
movl
leal
movl
int
start
$4,%eax
$1,%ebx
MSG,%ecx
LMSG,%edx
$0×80
movl
int
$1,%eax
$0×80
.data
.ascii
.long
’’Hallo, how are you?’’
.-MSG
Ergebnis dieses Programms ist die Ausgabe der Zeichenkette Hallo, how are
you?.
Das Quellprogramm besteht aus den beiden Bereiche .text und .data. Mit
.data wird ein Bereich (Segment) im Arbeitspeicher festgelegt, der zum Lesen und zum Schreiben verwendet werden kann, für den mit .text definierten
Bereich besteht nur Leseberechtigung.
Das .text-Segment bietet sich demnach für Assemblerbefehle und Konstante
, das .data-Segment für Variable an. Dies gilt auch für obiges Programmbeispiel.
Diese Aufteilung dient dem Schutz von Programmteilen und Konstanten gegen
irrtümliche Beschädigung. Beim Start wird dem Programm vom Betriebssystem Platz im Arbeitsspeicher für .text- und .data-Segment zugewiesen.
• .data-Segment
In obigem Beispiel wird im .data-Segment eine Zeichenkette definiert.
MSG ist der Name (die Marke, das Label, das Symbol) für das erste Byte
der Zeichenkette. Später wird dieser Name durch die reale Adresse (eine
Zahl) ersetzt. Die Direktive .ascii bewirkt, dass die zwischen Hochkommas
stehende Zeichenkette Zeichen für Zeichen nach der Ascii-Tabelle codiert
wird. Die Ascii-Codes werden hintereinander ab der Adresse MSG in den
Bytes des Arbeitsspeichers abgelegt.
An die Zeichenkette schließt sich ein Doppelwort (.long reserviert 4 Byte)
an, in dem das Ergebnis des mathematischen Ausdrucks .-MSG (“Punkt
minus MSG”) gespeichert wird. Das Zeichen “.” (Punkt) befragt den
location counter. Der location counter (Platzzähler) zählt die Belegung
des Arbeitsspeichers mit. Im obigen Beispiel hat der location counter
an dieser Stelle den Wert 19. Da die Adressen ab 0 gezählt werden, ist
dies die nächste freie Adresse. Der Ausdruck .-MSG liefert als Differenz
location counter - Anfangsadresse der Zeichenkette genau die Länge der
Zeichenkette. Bei Änderungen in der Zeichenkette wird die Länge immer
korrekt angepasst.
• .text-Segment
Im .text-Segment erscheint zunächst die Adresse start:. Diese Adresse
6
wird vom Betriebssystem benötigt. An dieser Stelle beginnt die Abarbeitung des Programmteils. Die nächste Zeile soll noch zurückgestellt und
später besprochen werden.
Die nachfolgenden Befehle lauten movl und leal. Mit movl wird der erste
Operand in den zweiten Operanden kopiert (nicht verschoben). Mit movl
$4, %eax wird beispielsweise der Festwert 4 in das Register eax kopiert.
$ kündigt einen Festwert an, % ein Register. Der Befehl leal MSG,%ecx
(lea=load effective address) ermittelt die Adresse der Marke MSG im
Arbeitsspeicher und speichert sie im Register ecx. Der angehängt Buchstabe l (OpSize-Suffix) zeigt an, dass Datentypen long (4 Byte) verwendet
werden.
Besondere Aufmerksamkeit soll dem Befehl int $0×80 gelten. Mit diesem
Befehl wird ein Satz von Funktionen angesprochen, der vom Betriebssystem
zur Verfügung gestellt wird. Diese Funktionen werden SystemCalls genannt.
Sie sind mit 0 beginnend fortlaufend nummeriert (siehe Tabelle 33). Um einen
ganz bestimmten der insgesamt 164 SystemCalls auszuwählen, müssen einige
Informationen übermittelt werden:
1. Die Nummer des gewünschten SystemCall muss ins Register eax kopiert
werden. Eine 1 im Register eax wählt den SystemCall exit, d. h. Programmende aus. Die im Programm ebenfalls verwendete 4 in eax wählt
den SystemCall write, die Ausgabe einer Zeichenkette. Während für Programmende keine weiteren Informationen benötigt werden, müssen für
die Ausgabe einer Zeichenkette noch Angaben über die Zeichenkette und
das Ziel der Ausgabe gemacht werden.
2. Die Ausgabe kann grundsätzlich auf Monitor, Drucker, Festplatte, etc.
erfolgen. Eine 1 im Register ebx führt zu einer Ausgabe auf dem Monitor
(StandardOut).
3. Das Objekt der Ausgabe, die Zeichenkette, wird durch die Anfangsadresse der Zeichenkette im Arbeitsspeicher und die Länge der Zeichenkette
(Anzahl der Bytes) festgelegt. Die Anfangsadresse hat in ecx, die Anzahl
der auszugebenden Bytes in edx zu stehen.
Wenn die Register mit den Informationen belegt sind, kann der gewünschte
SystemCall mit int $0×80 gestartet werden.
Aus der mit einem Editor (möglichst emacs) erstellten, für den Programmierer lesbaren Quelldatei, muss eine für den Prozessor verständliche, ausführbare
Datei gemacht werden. Dies ist in Abschnitt 4.3 näher erläutert. Dort sind auch
die dazu erforderlichen Kommandos dargestellt.
7
3
Primärer Speicher
Der im Rechner vorhandene Speicher wird in primären und sekundären Speicher eingeteilt. Der primäre Speicher (Arbeitsspeicher (Memory), Cache, Festwertspeicher, Register) ist überwiegend flüchtig (nicht-permanent), gestattet
aber schnelle Zugriffe (einige 10 ns und weniger).
Der sekundäre Speicher dient als permanenter Speicher zur Langzeitspeicherung. Er wird auch als externer oder peripherer Speicher bezeichnet. Hierzu zählen magnetische Medien (Festplatte, Floppy Disk, Magnetband, Kassette), magnetooptische Medien (MO-CD), optische Medien (CD-ROM) und auch
ältere Medien wie Lochstreifen und Lochkarten.
3.1
Speichertechnologien
Der Schreib-Lesespeicher (RAM) ist ein flüchtiger Speicher. Er wird entweder als dynamischer RAM D a t e n l e i t u n g
A d r e s s le itu n g
D a te n le itu n g
(DRAM) in MOS-Technologie oder als statischer
RAM (SRAM) in Bipolartechnologie realisiert. Die
G a te
G a te
MOS-Technologie gestattet eine extrem hohe PaS
D
D
S
ckungsdichte der DRAM-Speicherzellen, dadurch werden die Nachteile (destruktives Lesen, refresh-mode)
S p e ic h e r k o n d e n s a to r
überkompensiert. Der Speicherzustand des Grundelements (siehe Abbildung 1) wird durch den Ladezustand des Kondensators festgelegt. Die Ladung des
Speicherkondensators gibt die beiden Zustände Bit 0
(Kondensatorspannung UC unterhalb eines bestimm- Abbildung 1: Vereinfachtes Schaltten Wertes U1 z. B. 0,5 V) oder Bit 1 oberhalb eines bild einer DRAM-Speicherzelle: die
bestimmten Wertes U2 z. B. 5 V) wieder. Die Feld- Anschlüsse des Feldeffekttransistors
effekttransistoren haben die Funktion von Ventilen. sind neben Gate Source und Drain.
Durch eine Spannung am Gate-Anschluss werden die
in Abbildung 1 gestrichelt gezeichneten Strecken (Source-Drain-Strecke) leitend
gemacht und damit eine elektrisch leitende Verbindung zwischen den Datenleitungen und dem Speicherkondensator hergestellt. Dieser Vorgang wird Adressieren genannt. In diesem Zustand kann der Speicherkondensator entladen oder
geladen werden (Schreiben) oder die Ladung gemessen werden (Lesen).
In SRAMs bestehen die einzelnen Speicherzellen (siehe Abbildung 2) aus bistabilen Multivibratorschaltungen
(Flip-Flops). Die symmetrische Schaltung kann zwei stabile Zustände einnehmen (Bit 0 und 1). In einem Fall
liegt der Punkt P auf hohem Potenzial, Q auf niedrigem
Potenzial (z. B. Bit 1), im anderen Fall ist es umgekehrt
(Bit 0). Beim Adressieren werden durch Änderung der
Spannung auf der Adressleitung die beiden Dioden geöffnet. Damit wird das Lesen ( = Messen der Spannungen)
über die Datenleitung und Schreiben ( = gewünschten
Zustand herstellen) über die Datenleitung D ermöglicht.
Die Bipolartechnologie bedingt einen deutlich nied2:
Vereinfachrigeren Integrationsgrad, SRAMs haben aber wegen Abbildung
tes
Schaltbild
einer
SRAMdes schnelleren Zugriffes und der fehlenden refreshSpeicherzelle
Zeiten Geschwindigkeitsvorteile. Dementsprechend wer-
8
Abbildung 3: Registerstruktur der Intel PC-Prozessoren ab dem Typ 80368 mit
der Aufteilung der 32-Bit-Register des Intel PC-Prozessores in 16- und 8-Bit
Einheiten
den DRAMs für Arbeitsspeicher und SRAMS für schnellen Cache eingesetzt.
Die physikalische Struktur des Arbeitsspeichers besteht in einer linearen
Anordnung der Grundelemente, der Bytes. Jedes Byte besteht aus 8 Speicherzellen, die eine gemeinsame Adressleitung haben. Jedem Byte ist eine individuelle Zahl zugeordnet, über die es angesprochen werden kann. Diese Zahl wird
als Adresse bezeichnet. Die Adressen beginnen bei 0000 0000 und enden z.B.
bei 0fff fffc bei einem Arbeitspeicher von 256 MByte (Zahlenangaben hexadezimal). Im Arbeitsspeichers liegen für Benutzer zugängliche Speicherbereiche für
Programm-Code, Daten, Puffer, Stack ect. und reservierte Speicherbereiche für
Betriebssystem, Interruptadressen, Tastaturpuffer, Videospeicher, BIOS-ROM
ect.
Als Register wird ein interner Speicher des Prozessors bezeichnet, der zur
Zwischenspeicherung von Daten bei der Bearbeitung von Programmen dient.
Dabei handelt es sich dabei um eine eng begrenzte Zahl von besonders schnellen
Speicherzellen.
Lesespeicher (ROM) wird als Festwertspeicher bei PCs zur Speicherung
von Startprogrammen oder von Basisprozeduren benutzt (z. B. BIOS).
3.2
Register
Die ersten Versionen der Intel PC-Prozessoren waren mit 16-Bit-Registern ausgestattet, die die Namen ax, bx, cx, dx, si, di, bp, sp ip und eflag trugen (siehe
Abbildung 3). Die Register ax, bx, cx und dx waren und sind zweigeteilt. Sie
können als 16-Bit-Register, aber auch als 8-Bit-Register ah, al, bh, bl, ch, cl,
dh, dl angesprochen werden. Ab dem Prozessor 80386 sind die oben genannten
Register auf 32 Bit erweitert. Der Vorsatz e der Registernamen steht für exten-
9
ded. Damit verfügt der Intel PC-Prozessor heute über die 32-Bit-Register eax,
ebx, ecx, edx , von denen Teile als 16- oder 8-Bit-Register verwendet werden
können, und über die 32-Bit-Register esi, edi, ebp, esp, eip und eflag, deren niederwertige Teile auch als 16-Bit-Register angesprochen werden können (siehe
Abbildung 3).
Der GNU-Assembler as benutzt im Gegensatz zur Intel-Notation des MicrosoftAssemblers die sogenannte AT&T-Notation. Die Register des Intel PC-Prozessors
werden in der AT&T-Notation mit dem Zeichen % vor dem Registernamen bezeichnet, z. B. meint %esi das 32-Bit-Register esi, %al das niederwertige Byte
des 32-Bit-Registers eax.
3.3
Arbeitsspeicher
Der Arbeitsspeicher (Hauptspeicher, Memory) besteht aus einer Anordnung von ByteSpeicherzellen. Jeder Speicherzelle ist eine
Adresse zugeordnet, so dass der Arbeitsspeicher eine lineare Anordnung von gleichartigen
und gleichberechtigten 8-Bit-Speicherzellen darstellt. Er ist von 0 bis zum Ende des Adressraums durchnummeriert (siehe Tabelle 1). Um
eine Speicherzelle anzusprechen (zu adressieren),
müssen zwei Maßnahmen erfolgen:
32-Bit-Adresse
hexadezimal
ffff ffff
ffff fffe
ffff fffd
.
.
.
.
.
.
• Die Nummer der gewünschten Zelle muss
in einem Register, dem Adressregister enthalten sein.
• Die zur Zelle gehörigen Adressleitungen
müssen durch den Adressdekoder aktiviert
werden.
Speicherbyte =
8 1-Bit-Zellen
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
0000 0002
0000 0001
0000 0000
.
.
.
.
.
.
.
.
.
.
.
.
Im 32-Bit Adressregister können Adressen
von 0 bis 232 − 1 gespeichert werden. Damit Tabelle 1: Lineare Anordnung im durchkann also ein Bereich (Adressraum) von 4 GByte nummerierten Arbeitsspeicher. Je nach
adressiert werden. Das Adressregister kann vom Ausbau eines Rechners ist ein Teilbereich
Benutzer nicht direkt angesprochen werden.
des maximalen Adressraums realisiert.
Ein Schaltkreis im Prozessor, der Adressdecoder wählt auf Grund der Adresse (=Zahl im Adressregister) die Adressleitung zur angesprochenen Speicherzelle aus. Zur Adressierung eines bestimmten
Adressraumes ist eine der Größe des Adressraums entsprechende Anzahl von
Adressleitungen erforderlich. Möglichkeiten, die Anzahl der physikalischen Leitungen zu reduzieren, sind in [22] und [23] angegeben.
Für den Zugriff auf den Arbeitsspeicher werden häufig die zwei folgenden
Befehle benutzt (siehe auch Abschnitte 2 und 7). Mit dem Befehl
movl
NUM1,%ebx
wird der Inhalt des Doppelworts NUM1 in das Register ebx kopiert (l=long)
und mit
leal
NUM1,%ebp
10
wird die Adresse des Doppelworts NUM1 in das Register ebp kopiert. Für die
Ansprache von Wörtern oder Bytes werden die Endungen w bzw. b benutzt.
3.3.1
Schreiben und Lesen im Arbeitsspeicher, little endian
Ein großer Teil der Aktivitäten des Prozessors besteht im Transfer von Daten
zwischen dem internen Speicher (Register) und dem Arbeitsspeicher. Dies wird
als Speichern bzw. Laden (aus Sicht des Prozessors) bezeichnet. Der Transfer
wird in zwei Schritten ausgeführt:
• Transfer vom Prozessor in den Arbeitsspeicher (Speichern)
◦ Durch ein erstes Signal aktiviert der Prozessor über die Adressleitungen das zu belegende Byte. Dazu werden die Gates der Speicherzellen
geöffnet.
◦ Durch ein zweites Signal wird die zu speichernde Information über
die Datenleitungen in die aktivierte Zelle übermittelt. Dabei laden
die auf den Datenleitungen liegenden Spannungen (low/high) wegen der geöffneten Gates die Speicherkondensatoren (die eigentlichen
Träger der Informationen) auf die entsprechende Spannung auf.
• Transfer vom Arbeitsspeicher in den Prozessor
◦ Durch ein erstes Signal aktiviert der Prozessor über die Adressleitungen das zu lesende Byte. Dazu werden die Gates der Speicherzellen
geöffnet.
◦ Durch ein zweites Signal wird die gespeichernte Information über
die Datenleitungen gelesen. Dabei werden die wegen der geöffneten
Gates auf den Datenleitungen liegenden Spannungen (low/high) der
Speicherkondensatoren gemessen.
Aus mehreren Bytes bestehende Datenblöcke wie Wörter (2 Bytes), Doppelwörter (4 Bytes) oder weitere Datenblöcke (siehe Abschnitt 6) werden im Intel
PC-Prozessor im little endian-Modus gespeichert. Das bedeutet, dass das niederwertige Byte des Datenblocks bei niedriger Adresse abgelegt wird. Dies ist
in Tabelle 2 gezeigt. An der Adresse 0a100 steht das Wort 0x27a1 (=10.145),
dabei das niederwertige Byte 0xa1 an der niedrigeren Adresse 0a100, das höherwertige Byte an der höheren Adresse 0a101. Ab der Adresse aa100 ist das
Doppelwort 0x1f33702a (=523.464.746) in gleicher Weise gespeichert.
3.3.2
Schutzmaßnahmen im Arbeitsspeicher
Unter Linux arbeitet der Prozessor im protected mode. Dabei kann der gesamte Arbeitsspeicher (virtuell 4 GByte) durch 32-Bit-Register linear adressiert
werden. Es gibt keine Einteilung in 64-kByte Segmente, wie es bei MSDOS wegen der Abwärtskompatibilität zum ersten 64-kByte-Arbeitsspeicher-PC nötig
war. Die Verwendung von Segmenten oder Sektionen (sections, segments) unter Linux verfolgt einen anderen Zweck. Bei MSDOS konnte der gesamte Arbeitspeicher von 1 MByte ungeschützt angesprochen werden. Es bestand keinerlei Schutz gegen Überschreiten von Segmentgrenzen. Wenn z. B. durch fehlerhafte Adressierung der Bereich des Datensegments überschritten und dabei
11
Daten in den Bereich des Programmsegments geschrieben wurden, so wurde
dies durch keine Schutzmaßnahmen verhindert. Dies führte i. a. zu einem Absturz des Rechners.
Bei Linux als Multiuser-/Multitasking-Betriebssystem dienen Segmente zur Abgrenzung und zum Schutz gegen missbräuchliche Zugriffe
von außen. In diesem Sinn enthalten die Segmentregister im protected
Adressen Daten
mode nicht die Anfangsadressen der Segmente, sondern als Segment....
....
selektoren Informationen über die Lage und die Länge der Segmente
aa103
1f
und über die Privilegierungsniveaus.
aa102
33
Das GNU-Programmiersystem (GNU-Linker) benutzt die folgenaa101
70
den Segmente, die durch Direktiven definiert werden. Ein Überschreiaa100
2a
ten der Segmentgrenzen führt zu einem Programmabbruch mit der
....
....
Meldung unerlaubter Speicherzugriff bzw. segmentation fault.
....
....
0a101
27
• .text-Segment
0a100
a1
Das Segment .text dient zur Aufnahme des Programmcodes und
....
....
der Konstanten. Der Versuch, in diesem Segmentbereich Spei....
....
cherinhalte zu verändern, erzeugt eine Programmabbruch mit
00000
....
der Fehlermeldung unerlaubter Speicherzugriff oder segmentation fault.
Tabelle 2: Speicherung von
Datenblöcken (Die Zah• .data-Segment
len sind in hexadezimaler
Das Segment .data dient der Aufnahme von Variablen. Es ist
Form angegeben.)
auch möglich, Programmcode in diesen Segmentbereich zu speichern, jedoch besteht dann kein Schutz gegen Überschreiben.
• .bss-Segment
Das bss-Segment dient zu Aufnahme von Datenbereichen, die von verschiedenen Modulen gemeinsam genutzt werden (Common-Bereiche).
.bss-Segmente werden durch die Direktiven .comm und .lcomm angelegt.
3.4
Stack
Der Stack (“Stapel”) ist physikalisch im Arbeitsspeicher realisiert. Der Zugriff
auf Stackspeicherplätze erfolgt in der Regel nicht mit der üblichen Adressierung, sondern durch sequentielle Belegung oder Entnahme. Der Stack wird nach
dem Motto “last in, first out” bedient. Er ist als Zwischenspeicher, zur Übergabe von Parametern zwischen Prozeduren oder zur Reservierung von Speicher
für lokale Variable konzipiert und ist in Wörtern organisiert. In besonderen
Fällen kann es nötig sein, den Stack in der für den Arbeitsspeicher üblichen
Art zu adressieren (siehe Abschnitte 5.4 und 9.4).
3.4.1
Initialisierung des Stacks
Das Betriebssystem reserviert bei Programmstart im Arbeitsspeicher ein Bereich für die Verwendung als Stack. Die laufende Verwaltung des Stack erfolgt
mit dem Register esp. Bei Programmstart wird der Stackpointer esp auf das
obere Ende des Stackbereiches (Stack top) - genau betrachtet auf das erste Byte
über dem Stack - im Beispiel der Tabelle 3 die Adresse 10200h - ausgerichtet.
Der Stackpointer zeigt jeweils auf das letzte belegte Byte.
12
Das Betriebssytem initialisiert einen gemeinsamen Stackbereich für die Module eines Programms, so dass der Programmierer
keine eigenen Maßnahmen ergreifen muss.
Daneben ist es dem Programmierer möglich,
selbst einen Stackbereich zu definieren. Dies
kann dadurch geschehen, dass der Progammierer einen Bereich im Arbeitsspeicher reserviert, z. b. durch die Direktiven
STPL:
LSTPL:
.space
.long
0x1000
.-STPL
und, wie oben beschrieben, den Stackpointer esp auf das erste Byte oberhalb des reservierten Bereichs ausrichtet:
leal
addl
3.4.2
fffff
10200
. . .
101ff
101fd
101fb
101f9
101f7
101f6
101f7
. . .
10000
00000
Dat1
Dat2
Dat3
Dat4
Dat5
← esp
Tabelle 3: Stackverwaltung mit
Hilfe des Stackpointer esp. Im
Beispiele wurden die Wörter
Dat1, ... mit den Befehlen pushw
Dat1, ... auf den Stack gebracht.
Der Stack zeigt danach auf das
niederwertige Byte des Wortes
Dat5.
STAPL,%esp
LSTPL,%esp
Benutzung des Stack
Die Belegung des Stacks erfolgt von Stack
top ausgehend nach “unten”. In dem Stack
der Tabelle 3 war der Stackpointer esp ursprünglich auf die Adresse 10200 ausgerichtet (d. h. der Inhalt von esp war 10200).
Die auf dem Stack aufgeführten Wörter wurden in der Reihenfolge Data1, Data2, Data3, Data4 auf den Stackgebracht worden. Die Wörter sind jeweils mit
dem niederwertigen Teil (Low Byte) nach unten (in Richtung abnehmender
Adressen) angeordnet (“little endian”). Die Benutzung des Stacks erfolgt mit
folgenden Befehlen:
• Belegen des Stacks mit dem Wort wd bzw. dem Doppelwort dwd
pushw
pushl
wd
dwd
• Zurückspeichern in das Wort x2b bzw. in das Doppelwort x4b vom Stack
popw
popl
x2b
x4b
Durch den Befehl pushl opl (opl=Operand Typ long) wird der Stackpointer um 4 erniedrigt, dann wird das Doppelwort opl an der Adresse esp gespeichert. Danach ist der Stackpointer wieder auf das unterste belegte Byte
ausgerichtet.
Bei dem Befehl popw opw wird das Wort, auf das der Stackpointers in esp
ausgerichtet ist, vom Stack entfernt und in opw gespeichert. Dann wird der
Stackpointer (d. h. der Inhalt von esp) um 2 erhöht. Für die beiden anderen
Varianten pushw opw und popl opl gilt entsprechendes.
13
Eine wichtige Anwendung des Stacks ist die Parameterübergabe zu und von
Prozeduren. Dabei wird abweichend von den oben beschriebenen Adressierung
eine “normale” Adressierung benutzt. Dies ist näher in den Abschnitten 5.4
und 9 beschrieben.
14
4
Erstellung eines Programms
4.1
Einführung
Die praktischen Übungen werden mit dem GNU-Assembler unter Linux durchgeführt. Hierzu sind Grundkenntnisse im Betriebsystem Unix bzw. Linux erforderlich. Im Rechnerraum ist Linux des Distributors S.u.S.E. installiert. Für
die Durchführung der Übungen werden folgende Programme benötigt:
• Editor emacs, vi oder andere
• Assembler as
• Linker ld
• Debugger gdb
• Werkzeuge wie objdump, nm, size, ar, ...
• Systemfunktionen (System Calls) wie read, open, ....
Sämtliche Programme sind Bestandteil der Linux-Installation. Informationen sind ausschließlich online erhältlich. Werden Informationen zu einem bestimmten Schlüsselwort z. B. objdump gesucht, können diese mit man objdump
(oder z. B. man as für den Assembler oder man 2 read Systemfunktion read
in Manual 2,) und info objdump (oder z. B. info emacs für Informationen zum
Editor emacs) eingeholt werden. Ohne vorhergehende Angabe eines Schlüsselwortes kann mit tkinfo, tkman oder xman (beides per Befehlszeile oder Pulldownmenu erreichbar) oder mit Susehilf (Menu) bzw. hilfe (Befehlszeile) nach
Informationen gesucht werden.
4.2
Grundstruktur eines Assemblerprogramms
/* Formaler Rahmen eines Assembler Quellprogramms */
/* Beginn des text-Segments für Programmcode und */
/* Konstante */
.text
/* Ab der Adresse beginnt die Bearbeitung von Programmcode */
start:
/* Anfangsadresse des Programmcodes wird an den Linker gemeldet */
.globl start
/* Programmkörper */
<Assemblerbefehle>
. . . . . . . .
. . . . . . . .
/* Programmbeendigung: SystemCall 1 = exit */
movl
$1,%eax
int
$0×80
/* Beginn des data-Segments für Daten */
.data
15
/* Ab der Adresse MAG wird eine Zeichenkette initialisiert */
MAG:
.ascii
‘‘Hallo, how are you?’’
/* Location counter (.) minus Adresse MAG ergibt die */
/* Länge der Zeichenkette */
LMAG:
.long
.-MAG
ZAHL:
.long
3,437,0xffe8,0b110110001
.byte
65, 0x41,0101, 0b1000001
.space
0x100
. . . . . . . .
. . . . . . . .
4.3
Erstellung eines lauffähigen Programmmoduls
Die Programmerstellung läuft in den Schritten Zeichnen eines Ablaufplans,
Editieren, Assemblieren, Linken und natürlich Ausführen ab.
4.3.1
Ablaufdiagramm
Der erste Schritt zum Aufbau eines Assemblerprogramms ist die Analyse des
Problems und die Ermittlung eines geeigneten Algorithmus (Lösungsverfahrens). Der Algorithmus wird in einem Ablaufplan formuliert, der die Grundlage
für die Codierung in einer speziellen Assemblersprache darstellt.
4.3.2
Editieren
Mit Hilfe eines Editors wird der Ablaufplan nach den Regeln der Assemblersprache in eine Quelldatei umgesetzt, die die Befehle in mnemonischer Form
(Mnemo-Code) und die Direktiven enthält. Befehle sind Anweisungen an den
Prozessor, gewisse Maßnahmen durchzuführen, z. B. von einem Register in den
Arbeitsspeicher zu kopieren, den Stack zu bedienen, Register zu vergleichen. Sie
werden in für den Programmierer verständlicher Form geschrieben (Mnemocode) und müssen in eine dem Prozessor verständliche Form (Maschinencode) gebracht werden. Direktiven sind Anweisungen, die nicht zu Prozessoraktivitäten
führen. Sie regeln den Ablauf eines Programms, die Anordnung der Segmente im Arbeitspeicher, bieten dem Programmierer Hilfen und Vereinfachungen,
geben Informationen für die weitere Verarbeitung des Programms weiter. Die
Syntax sieht vor, dass Direktiven mit einem Punkt beginnen (z. B. .ascii “hallo”
reserviert 5 Byte und belegt sie mit den angegebenen Zeichen bzw. mit deren
ASCII-Codes).
4.3.3
Assemblieren
Der Assembler as wandelt unter Berücksichtigung der Direktiven den Mnemocode in den Maschinencode um, er erzeugt aus der Quelldatei eine Objektdatei. Der Assembler löst zunächst Makros und ähnliche Programmierhilfen auf
und legt eine Tabelle der Namen (Variable, Konstante und Labels) und der zugehörigen Adressen an. Danach werden die Assemblerbefehle in Maschinencode
umgewandelt, wobei die Namen durch Adressen in Zahlenform ersetzt werden.
Die Informationen über die verwendeten Namen bleiben in der Objektdatei
16
Mnemo-Code
movl %ebx,%eax
movw %bx,%ax
movb %bl,%al
jmp LB
Maschinencode
8b d8
66 8b d8
eb 14
cbtw
cwtl
addw %bx,%di
66 98
98
66 01 fb
addl %ebx,%edi
01 fb
Aktion
Inhalt des Registers ebx wird nach eax kopiert
Inhalt des Registers bx wird nach ax kopiert
Sprung zum 14h Programmzeilen entfernten
Label LB
Erweitern des Byte al zum Wort ax
Erweitern des Wortes ax zum Doppelwort eax
Addition der Inhalte der Register di und bx,
Ergebnis in di
Addition der Inhalte der Register edi und ebx,
Ergebnis in edi
Tabelle 4: Beispiele von Befehlszeilen, das sog. OpSize-Präfix 66 bewirkt Umschalten zur 16-Bit-Operandengröße, da im protected mode 32-Bit-Größe Standard sind.
enthalten. In Tabelle 4 sind einige Beispiel für Assemblerbefehle in Mnemound Maschinencode angegeben.
Die Reihenfolge der Befehle und der Datendeklarationen im Maschinencode
und in den Speicherreservierungen ergibt sich genau aus deren Reihenfolge in
der Quelldatei. Jede vom Assembler as erzeugte Objektdatei enthält mindestens 3 Segmente, die auch leer sein können. Als Erstes wird das .text-Segment
angelegt, gefolgt vom .data und dem .bss-Segment. Im Objekt-Status beginnen
alle Segment mit der Adresse 0. Da in der Regel ein ausführbares Programm
aus mehreren Objektdateien hervorgeht, ist es nötig, die Objektdateien zusammenzufügen und aufeinander abzustimmen.
4.3.4
Linken
Der Linker ld ordnet den Objektdateien, die zu einem ausführbaren Programm
zusammengebunden werden sollen, Endadressen (runtime-Adressen) zu, so dass
keine Überlappung auftritt. Die Segmente werden als feste Blöcke behandelt,
die weder in ihrer Länge noch in der Reihenfolge der Bytes innerhalb der Segmente Änderungen erfahren. Weiterhin werden die Aufrufe von Objektdateiadressen an die runtime-Adressen angepasst. Diese Maßnahmen des Linkers
werden als Relokation (Relozieren) bezeichnet. Unter Relokation versteht man
also das Zuweisen der runtime-Adressen zu den Segmenten und das Anpassen
der Aufrufe von Objektdateiadressen an die runtime-Adressen.
Betrachten wir in Abbildung 4 die beiden Objektdateien obdat1.o und
obdat2.o. Jedes Segment beginnt bei der Adresse 0. Unabhängig davon, in
welcher Reihenfolge die .text-, .data- und .bss-Segmente in der Quelldatei angeordnet sind, findet sich nach dem Linken in der ausführbaren Datei die in
Abbildung 4 angegebenen Reihenfolge. Auch wenn in der Quelldatei mit den
Direktiven .text1, .text2 usf. eine Gliederung versucht wird, werden alle .textSegmente zu einem .text-Segment zusammengefasst. Entsprechendes gilt für
die anderen Segmente.
Um ein lauffähiges Programm erzeugen zu können, müssen dem Linker meh-
17
Abbildung 4: Zusammensetzung von Objektdateien durch den Linker
rere Informationen übergeben werden (z. B. Name der ausführbaren Datei, Anfangsadresse, Format der Objektdatei, ... ). Dies wird i. a. in standardisierter
Form durchgeführt. Erwähnenswert ist dabei, dass als Anfangsadresse start:
erwartet wird und dass die ausführbare Datei den Namen a.out erhält, wenn
in der Befehlzeile des Linkers nichts anderes angegeben wird.
Die folgenden Befehlszeilen benutzen die Option o, die dem Benutzer erlaubt, Namen für die Objektdatei und die ausführbare Datei festzulegen.
• Assemblieren: as -o datname.o datname.s
Die Quelldatei datname.s wird assembliert. Das Ergebnis ist die Objektdatei datname.o .
• Linken: ld -o datname.x datname.o /home/stud/liste.o liba1.a
Die Objektdateien datname.o und /home/stud/liste.o und die Bibliotheksdatei (Archiv) liba1.a werden zusammengebunden. Das Ergebnis
ist die ausführbare Datei datname.x.
Nähere Informationen sind mit man as bzw. man ld erhältlich (siehe auch
Abschnitt 4.1).
4.4
Werkzeuge zur Programmentwicklung
Für die Erstellung von Programmen, insbesondere zur Fehlererkennung und
Fehlerbehebung gibt es eine Reihe von Werkzeugen, die in Verzeichnis /usr/bin/
enthalten sind.
4.4.1
Der GNU-Debugger
Bei Verwendung des Debuggers wird die ausführbare Datei (das Programm)
durch Setzen der trap-flag (siehe Abschnitt 8.1) in Einzelschritten durchlaufen.
Es ist möglich, breakpoints zu setzen, an denen das Programm angehalten und
durch Betrachten von Register, Stack oder Arbeitsspeicher auf logische Korrektheit geprüft werden kann. Detaillierte Informationen sind unter info gdb,
Menüpunkt Index zu finden. Auf den GNU-Debugger setzt der Debugger DDD
auf, der eine graphische Oberfläche bietet.
18
Start des Debuggers: gdb file.x (Zu untersuchende ausführbare Datei). Danach meldet sich der Debugger mit . . (gdb) und ist
bereit, Kommandos entgegenzunehmen.
Beenden:
quit, kurz q oder Strg-D
Kommandos:
Eingabezeile, die mit einem gdb-Kommando beginnt.
Es genügt, die Anfangsbuchstaben einzugeben, bis das
Kommando eindeutig festgelegt ist, danach evtl. TAB .
Eine leere Eingabezeile, d.h. ret wiederholt den vorangegangenen Befehl (Ausnahme: list und x).
Start des Programms: run, kurz r
Programm-Status:
info program
Breakpoints:
Setzen: break LABEL-Bezeichner, break ProzedurName oder break Dateiname, Prozedur-Name oder
break *0xhex Adresse oder break (nächste Instruktion).
Information über Breakpoints: info break.
Löschen: clear Prozedur-Name oder clear DateiName:Prozedur-Name. delete Breakpoint-Nr. oder
delete (alle)
Aktivieren/Deaktivieren: enable Breakpoint-Nr.
oder enable (alle) bzw. disable Breakpoint-Nr. oder
disable (alle)
Fortsetzung:
continue, kurz c oder nur die nächste Instruktion stepi, kurz si evtl. mit Zahl der auszuführenden Instruktionen. Bei einem Prozeduraufruf sorgt nexti, kurz ni für
die Ausführung der gesamten Prozedur und hält danach
wieder an.
Stack:
backtrace zeigt Reihenfolge der Prozeduraufrufe mit
den Nr. der Stack-Frames an. frame Nr. wählt den entsprechenden Stack-Frame aus. Das Hauptprogramm hat
den frame mit der höchsten Nr., die gerade aktive Prozedur hat den frame mit der Nr. 0.
info frame gibt Informationen über den aktuellen
Stack-Frame aus.
Speicherinhalte:
x/NFU 0xHex-Adresse gibt Speicherinhalte ab der angegebenen Adresse aus: N=Anzahl der Speichereinheiten, F=Format, U=Einheitengröße(b=Byte, h=Wort,
w=Doppelwort, g=Quadwort). Soll ein Registerinhalt
als Adresse verwendet werden, so ist dem Registernamen (ohne %) ein $ voranzustellen.
Registerinhalte:
info registers oder info all-registers oder info
registers $regname gibt die Registerinhalte aus. Besondere Register: $pc(program counter), $sp(stack pointer), $fp(frame pointer), $ps(processor status)
Adresse von Symbolen:info address NAME
Weitere Information: help info
4.4.2
Spezielle Werkzeuge
Die folgende Auswahl von Werkzeugen enthält Programme zum Disassemblieren von Objektdateien oder auführbaren Dateien (objdump), zum Auflisten
19
bzw. Entfernen der in einer Objektdatei benutzten Namen (nm bzw. strip)
und zur Ausgabe der Startadresse und der Segment- und Gesamtgrößen einer
Objektdatei (size).
• Darstellung von Objektdatei oder ausführbarer Datei
Befehlszeile: objdump datei.o bzw. datei.x -Option
Option Wirkung
d
Disassemblieren des Textsegments
D
Disassemblieren des Textsegments und des Datensegments
h
Ausgabe der Größe und Adressen der Segmente
s
Kompakte Darstellung des gesamten Programms in
hexadezimaler Form
• Namen (Symbole) aus der Objektdatei auflisten
Befehlszeile: nm datei.o -Option
Option Wirkung
o, A
Dateinamen voraussetzen
n
nach Adressen sortieren
p
unsortiert
• Namen (Symbole) aus der Objektdatei entfernen
Befehlszeile: strip datei.o -Option
Option Wirkung
s
alle Namen entfernen
x
nur lokale Namen entfernen
Nname Namen name entfernen
Kname Namen name retten
• Startadress, Segment- und Gesamtgröße auflisten
Befehlszeile: size datei.o -Option
Option Wirkung
in Zeilen
A
In Spalten
B
in Zeilen
4.5
Assembleranweisungen
Die Struktur von Befehlszeilen im Assembler-Quellprogramm ist in Tabelle 5
zu sehen. Anweisungen (statements) treten in zwei Formen auf:
• Befehle (instructions) sind Anweisungen an den Prozessor und werden
in Maschinencode übersetzt. Sie geben dem Prozessor vor, was gemacht
werden soll.
• Direktiven (directives) sind Anweisungen an den Assembler oder Linker,
sie werden nicht in Maschinencode übersetzt. Direktiven regeln, wie etwas
gemacht werden soll.
Im Quellprogramm angegebene Zahlen werden vom Assembler in die interne binäre Darstellung umgewandelt. Sie können binär, oktal, dezimal oder
hexadezimal vorgegeben werden. Die entsprechende Syntax ist in Tabelle 6
angegeben.
20
Name/
Marke
MLDG:
Befehl/
Direktive
.ascii
Operanden
Kommentar
”Hallo Du”
leal
MLDG,%eax
addl
%ecx,%eax
#
#
#
#
#
#
Beginnend mit der Marke MLDG
wird die Zeichenkette ‘Hallo Du‘
gespeichert
Speichert die Adresse von MLDG
in eax
Addiert den Inhalt von ecx zu eax
Tabelle 5: Struktur von Befehlszeilen im Assembler-Quellprogramm
Basis
binär
Synthax
0b..... / 0B.....
oktal
dezimal
hexadezimal
0.....
..... (ohne führende Null)
0x.....
Beispiel
0b111100110100 oder
0B111100110100
07464
3894
0xf34
Tabelle 6: Schreibweise von Zahlen mit verschiedenen Basiswerten
Kommentare stehen zwischen den Zeichen /* ... */ oder hinter dem Zeichen #. Kommentare werden vom Assembler ignoriert, d. h. vergrößern das
ausführbare Programm nicht. Sie sind sehr wichtig für die Lesbarkeit. Kommentare können in der Form /* ... */ auch zur Gliederung der Quelldatei
verwendet werden. Damit wird die Struktur des Programms deutlicher und
das Programm besser lesbar.Mit # können einzelne Befehle in derselben Zeile
erläutert werden. Im Kommentar sollten keine reservierten Namen verwendet
werden.
21
5
Adressierung
Daten, die im Arbeitsspeicher abgelegt sind, können durch Angabe der dem
Speicherplatz zugeordneten Nummer, der Adresse (siehe Abschnitt 3.3) aus
dem Arbeitsspeicher in ein Register geladen werden, umgekehrt können auf
diese Weise Daten im Arbeitsspeicher gespeichert werden. Dieses Ansprechen
von Speicherplätzen im Arbeitsspeicher wird Adressierung genannt.
Die Adressierungsmöglichkeiten des Assemblers sind die direkte, die indirekte und die indizierte Adressierung. Bei der direkten Adressierung wird die
Adresse des gewünschten Bytes, Wortes oder Doppelwortes direkt, d. h. als
Namen oder Ausdruck, der den Namen enthält, angegeben. Bei der indirekten
Adressierung wird der Inhalt eines Register als Adresse des anzusprechenden
Wortes interpretiert. Bei der indizierten Adressierung enthält ein Register einen
Laufindex, der es erlaubt, in einer Schleife eine Liste von Bytes, Wörtern und
Doppelwörtern zu durchlaufen.
5.1
Direkte Adressierung
Die direkte Adressierung erfolgt durch die Angabe von Namen (Symbolen, Labels) . Beim Assemblierungsvorgang setzt der Assembler die Namen in Adressen
um. Die Daten werden in der Reihenfolge ihres Auftretens berücksichtigt. Dabei stellt der Name die Adresse des ersten Elementes eines Datenblocks dar.
Betrachten Sie die nachfolgende Definition eines data-Segments.
MSG1:
ZAHL:
.data
.ascii
.word
.text
‘‘Zeichenkette’’
3287,2,19786
Die Adresse MSG1 bezieht sich beispielsweise auf das Zeichen “Z”, die Adresse ZAHL auf das LowByte der (Wort-)Zahl 3287. Hinter der Adresse ZAHL sind
3 Integer-Zahlen definiert. Für die Adressierung weiterer Elemente steht eine
Adressarithmetik zur Verfügung, die in den folgenden Zeilen erläutert ist. Denken Sie dabei an die Zahlendarstellung im little endian-Modus, beschrieben in
Abschnitt 3.3.1.
movl ZAHL+2,%edx
movb MSG1+13,%al
movw MSG1+13,%ax
#
#
#
#
1.296.695.298 = 0x4d4a0002
wird nach edx kopiert
12 = 0xc wird nach al kopiert
524 = 0x20c wird nach ax kopiert
Beim Lesen der Segmentdefinition legt der Assembler eine Liste der Namen
und der zugehörigen Adressen an. Im location counter wird die Belegung des
Arbeitsspeichers registriert, der location counter zeigt jeweils das nächste freie,
d. h. belegbare Byte an. Treten beim weiteren Assemblierungsvorgang Namen
auf, so werden diese durch die Adressen aus der Liste ersetzt.
Überprüfen Sie dies dadurch, dass Sie ein Programm (z. B. start.o oder
start.x) mit Hilfe des Werkzeugs objdump betrachten.
22
5.2
Indirekte Adressierung
Eine flexiblere Verwendung von Größen (Variablen oder Konstanten) wird
durch die indirekte Adressierung erreicht. Dabei treten die Adressen als Registerinhalte auf. Durch die Schreibweise (%ebp) - also %ebp in runden Klammern
- wird deutlich gemacht, dass nicht der Inhalt des Registers ebp angesprochen
wird, sondern der Inhalt des Speicherbytes, dessen Adresse in ebp steht. Diese
Adresse wird Basisadresse genannt. Mit einem vorgesetzten Festwert (displacement) kann im Arbeitsspeicher eine bestimmte Anzahl von Bytes weiter- oder
zurückgegangen werden: 5(%ebp) entspricht MSG1+5, wenn ebp die Adressse
von MSG1 enthält. Nach den Befehlen (Bezug auf das Beispiel in Abschnitt
5.1):
leal
movb
MSG1,%ebp
(%ebp),%dl
steht im Register dl der ascii-Wert von “Z”, also die Zahl 90. Mit dem Befehl
movb
2(%ebp),%dl
wird das Zeichen “i” (ascii-Wert 105) ins Register dl kopiert.
Frage: Welches Zeichen wird mit
leal
movb
ZAHL,%ebp
-5(%ebp),%ch
wohin kopiert?
Die wichtigste Anwendung der indirekten Adressierung besteht in der Parameterübergabe zwischen Prozeduren. Dies wird in Abschnitt 5.4 ausführlich
behandelt.
5.3
Indizierte Adressierung
Die indizierte Adressierung erweitert die Möglichkeiten der indirekten Adressierung. Sie wird dazu benutzt, Listen von Bytes, Wörtern, Doppelwörtern ect.
elementeweise in einer Schleife zu durchlaufen. Der Laufindex wird in einem
Indexregister gehalten und durch die Befehle incl oder decl erhöht oder erniedrigt. Die Schrittweite der Änderung (Skalierung) der Adresse hängt von der
Blockgröße der Liste ab und kann 1, 2, 4 oder 8 betragen.
Schreibweise der Adresse:
Displacement(Basisregister,Indexregister,Skalierung)
Die zugehörige Adresse wird nach der Beziehung
Effektive Adresse=
Basisregister+(Indexregister*Skalierung)+Displacement
berechnet. Der Ausdruck stellt eine Adresse im Arbeitsspeicher dar und wird
entsprechend in Befehlen verwendet. Mit
movb 5(%ebp,%esi,2),%dl
wird das Byte von der mit 5(%ebp,%esi,2) angesprochenen Speicherstelle nach
dl kopiert.
Das folgende Beispiel zeigt die indizierte Adressierung bei der Adressierung
einzelner Bytes aus einer Liste, die die Zeichenkette Assemblerprogrammierung
macht Spass enthält.
23
.data
.ascii
‘‘Assemblerprogrammierung macht Spass’’
.text
/* Anfangsadresse der Liste ins Basisregister */
leal MS,%ebp
MS:
/* Indexregister esi mit 3 initialisieren */
movl $3,%esi
/* Das Zeichen e wird nach dl kopiert */
movb (%ebp,%esi),%dl
/* alternativ movb MS(,%esi),%dl */
Im einem weiteren Beispiel werden die ersten 30 Byte des
Datensegments mit dem Zeichen x bzw. dessen ascii-Code belegt. Hier erfolgt der typische Einsatz der indizierten Adressierung in einer Schleife.
Die Schleife ist kopfgesteuert und verwendet das Register
esi als Indexregister. Nach jedem Durchlauf wird esi inkrementiert und mit dem Register ecx verglichen, das die Anzahl der
Durchläufe enthält. Der Befehl cmpl steht für compare. Der
nachfolgende Befehl jne (jump if not equal) bewirkt einen
Sprung nach label LB1, wenn esi noch nicht gleich ecx ist, anderenfalls wird die nächste Programmzeile ausgeführt, d. h. die
Schleife verlassen (weitere Sprungbefehle in Tabelle 18).
LST:
.data
.space
.text
. . .
movl
leal
movl
128
Speicher-bytes
Adressen/
Namen
ffff ffff
FELD2+4
FELD2+3
FELD2+2
FELD2+1
FELD2
FELD1+4
FELD1+3
FELD1+2
FELD1+1
FELD1
$30,%ecx
LST,%ebp
$0,%esi
LB1:
movb
incl
cmpl
jne
$’x’,(%ebp,%esi)
%esi
%ecx,%esi
LB1
Im letzten Beispiel wurde die Anfangsadresse der Liste ins
Basisregister kopiert. Im Nächsten wird als Alternative die Anfangsadresse durch das Displacement festgelegt. Dabei wird das
Basisregister nicht benutzt. Seine Stelle bleibt leer, das nachfolgende Komma muss aber geschrieben werden.
LST:
.data
.space
.text
. . .
movl
FELD+4
FELD+3
FELD+2
FELD+1
FELD
0000 0000
Tabelle 7: Adressierung
von 2-dimensionalen Listen
128
$0,%esi
LB2:
24
movb
incl
cmpl
jne
$’x’,LST(,%esi)
%esi
$30,%esi
LB2
Fehlen die Angabe von Indexregisters und/oder Displacement, werden sie
durch Null ersetzt, ein fehlender Skalierungsfaktor wird als Eins angenommen.
Betrachten Sie die Liste (siehe Tabelle 7):
FELD
.space
1600
FELD stelle eine Liste aus 1600 Byte (oder 800 Wörtern oder 400 Doppelwörtern) dar und kann mit 1600 Bytes, 800 Wörtern oder 400 Doppelwörtern
belegt werden. Mit der Befehlssequenz
SCHL1:
leal
FELD,%ebp
movl
$0,%esi
movl
$400,%ecx
movl
(%ebp,%esi,4),%edx
/* movl FELD(,%esi,4),%edx */
incl
%esi
cmpl
%ecx,%esi
jne
SCHL1
wird die gesamte Liste FELD durchlaufen und Doppelwort für Doppelwort
nach edx kopiert. Mit
movl
3(%ebp,%esi,4),%edx
wird mit dem 4. Doppelwort begonnen. Allerdings erhält man unter Umständen
wegen Bereichsüberschreitung die Fehlermeldung unerlaubter Speicherzugriff
oder segmentation fault. Mit
movw
(%ebp,%esi,2),%dx
wird bei jedem Schleifendurchlauf ein Wort, also eine Folge von 2 Bytes in das
Register dx kopiert werden.
Die in Tabelle 7 gezeigten Listen können als 2-dimensionales Feld (Matrix)
aufgefasst werden. Zum Durchlaufen der Matrix werden 2 Indizes (z. B. die
Register esi und edi) benötigt. Es ist Sache des Programmierers durch geeignete Schleifenbbildung das gewünschte Durchlaufen der Matrix (spalten- oder
zeilenweise) zu organisieren.
Aufgabe: Eine Matrix (n Spalten, m Zeilen) ist im Arbeitspeicher zeilenweise abgelegt (siehe Tabelle 7). Schreiben Sie je ein Programmteil, in dem die
Matrix zeilenweise und spaltenweise durchlaufen wird.
5.4
Parameterübergabe in Prozeduren
Die indirekte Adressierung wird insbesondere bei der Parameterübergabe von
einer rufenden Prozedur bzw. vom Programm zur gerufenen Prozedur oder bei
Beendigung der Prozedur zur Rückgabe von Parametern angewandt.
25
Die üblicherweise benutzte und schnellsParameter1
te Ansprache des Stack ist in Abschnitt
Parameter2
3.4.2 geschildert. Sie erfolgt mit den BefehParameter3
len pushl und popl bzw. pushw und popw.
Rücksprungadresse
← esp
Dabei wird der Stack, wie in Abschnitt 3.4.2
bzw. Tabelle 3 gezeigt, von oben (stack top)
Parameter1
her belegt und nach oben hin abgetragen.
Parameter2
Dennoch ist in manchen Situationen ein
Parameter3
normaler d. h. wahlfreier Zugriff auf DaRücksprungadresse
ten innerhalb des Stack erforderlich. Hierzu
ebp
← ebp, esp
kurz die Beschreibung der Problematik in
Vorgriff auf Abschnitt 9.4:
Tabelle 8: Oben Stackbelegung bei Aufruf einer
Beim Aufruf von Prozeduren mit dem
Prozedur und unten nach “Einfrieren” des akBefehl call wird die Adresse (“Rücksprungtuellen Stackpointers esp im Register ebp (jeadresse”), an der das Programm später fortdes Kästchen entspricht vier Bytes, der Poingesetzt werden soll, auf dem Stack gesichert.
ter zeigt jeweils auf das “unterste” Byte).
Nach Beendigung der Prozedur mit ret kann
das Programm an der richtigen Stelle fortgesetzt werden. Vor dem Aufruf der Prozedur müssen die Parameter, die an
die Prozedur übergeben werden sollen, auf den Stack geschrieben werden.
In Tabelle 8 (oben) ist eine Stackbelegung mit 3 Parametern direkt nach
Aufruf einer Prozedur gezeigt. Würde jetzt mit dem üblichen Stackbefehl popl auf die Parameter zugegriffen werden, würde die Rücksprungadresse verlorengehen und eine ordnungsgemäße Weiterführung des Programms unmöglich
gemacht. Die Lösung des Problems besteht darin, dass der Stackpointer, der
die Stackbelegung kontrolliert, sich also ständig ändert, in das Register ebp
gespeichert, sein aktueller Stand gewissermaßen eingefroren wird. Zuvor muss
der ursprüngliche Inhalt des Registers ebp auf dem Stack gesichert werden (Tabelle 8 unten). Mit der indirekten Adressierung disp(%ebp) kann nun auf die
Parameter zugegriffen werden (z. B. mit disp = 12 auf Parameter2), ohne die
der Rücksprungadresse zu entfernen. Der dafür nötig Rahmen der Prozedur
hat folgendes Aussehen:
PRZNAME:
.globl PRZNAME
pushl
%ebp
# Sicherung des ‘‘alten’’ Inhalts von ebp
movl
%esp, %ebp # aktueller Stand von esp wird in ebp
# festgehalten
pushal
# Sicherung der 8 Register eax, ..., edi
/* Anfang des Prozedurkörpers */
. . . . . .
movl
16(%ebp),%eax
# Beispiel: Parameter1 wird nach eax kopiert
. . . . . .
/* Ende des Prozedurkörpers */
popal
popl
ret
%ebp
# Restaurierung der 8 Register eax, ..., edi
# Restaurierung von ebp
# Rücksprung zum rufenden Programm
26
Eine ausführlichere Beschreibung des Aufbaus, der Benutzung und der Einbindung von Prozeduren ist in Abschnitt 9 gegeben.
27
6
Datendefinitionen
Beim Assemblierungsvorgang erstellt der Assembler eine Objektdatei, die die
Maschinenbefehle und Speicherreservierungen für die Daten enthält. Im Assemblerprogramm muss also der Umfang des für Daten benötigten Speicherbereiches festgelegt werden. Dies geschieht dadurch, dass die verwendeten Variablen und Konstanten mit ihrem Speicherbedarf innerhalb der Definition
des Daten- bzw. Textsegments aufgeschrieben werden. Durch den Assembler
wird für die Größen (Variable und Konstante) in der Reihenfolge ihres Auftretens im Quellcode Speicherplatz reserviert. Gleichzeitig werden den Namen der
Größen Adressen zugeordnet. Zusätzlich zur Reservierung von Speicherplatz ist
es möglich, die Größen vorzubelegen. Zu beachten ist, dass im .text-Segment
definierte Datenblöcke als Konstante zu verwenden sind, d. h. nicht verändert
werden können (read only), Variable werden im .data-Segment definiert.
Datenblock
Byte
Byte
Wort
Doppelwort
Quadwort
Oktawort
Anz.Bytes
1
1
2
4
8
16
Direktive
.ascii zeichenkette
.byte ausdrücke
.word ausdrücke
.long ausdrücke
.quad ausdrücke
.octa ausdrücke
Verwendung
Zeichen
Zahlen
Zahlen, Adressen
Zahlen, Adressen
Zahlen
Zahlen
Tabelle 9: Direktiven für die wichtigsten Datenblöcke im Prozessor 80 x86
6.1
Adressierung der Datenblöcke
Die zur Definition von Zeichen und ganzen Zahlen zur Verfügung stehenden Direktiven sind in Tabelle 9 angegeben. Die Gleitkommazahlen sind in Abschnitt
14.2 behandelt. Die Namen der Daten repräsentieren die Adressen jeweils des
ersten Byte des definerten Datenbereichs. Betrachten wir die Programmzeile:
ZAHL:
.long
0x12345678, 2882369680
Mit der Direktive .long werden 2 Doppelwörter (8 Byte) im Arbeitspeicher
reserviert und mit den angegebenen Zahlenwerten initialisiert. Das erste (niederwertige) Byte (0x78) wird an der Adresse ZAHL angelegt (little endian,
Abschnitt 3.3.1). Mit dem Befehl
movl
ZAHL,%eax
wird die erste Zahl (12345678hex =305419896) in das Register eax kopiert, mit
dem Befehl
movl
ZAHL+4,%eax
28
Abbildung 5: Mit Bezeichnungen wie High/Low Order Half, High/Low Order
Word, High/Low Order Byte oder High/Low Order Bit werden Teile von Datenblöcken angesprochen. Die High/Low Order Bits werden auch least/ most
significant bits (lsb/msb) genannt.
die um 4 Byte darüberliegende Zahl
(2882369680).
6.2 Strukturierung der Datenblöcke
Die Anordnung der Bytes von Wort- bzw.
Doppelwortzahlen sind in Tabelle 10 mit
den Daten ZAHL1 und NUM erläutert.
Aufgabe: Tragen Sie die Byte-Werte
von ZAHL2 in die Tabelle 10 ein.
Bei der Reservierung von Speicherplatz
für Datenblöcke wie Wörter, Doppelwörter
etc. werden Bytes lediglich in logischer Form
zusammengefasst, physikalisch bestehen sie
aus unabhängigen Bytes. So kann in einem
solchen Datenblock ohne weiteres ein einzelnes Byte adressiert und herausgegriffen
werden. Mit movw ZAHL1+1,%ax (siehe Abbildung 10) wird die Zahl 0x6512 nach ax
kopiert.
Aufgabe: Welches Doppelwort wird
mit movl ZAHL1+3,%edx nach edx kopiert?
Die Beispiele zeigen, dass es innerhalb
eines Segments für Datenblöcke keinen Bereichsschutz gibt. Wenn bei der Adressierung eines Feldes die Grenzen überschritten werden, wird auf die nächsten Speicherplätze, auch auf Befehlszeilen zugegriffen. Allerdings besteht ein Schutz gegen Überschreitung der Segmentgrenzen
und beim .text-Segments überhaupt gegen
Überschreiben innerhalb dieses read-onlySegments.
Die Interpretation des Speicherinhaltes,
d. h. ob das an sich wertneutrale Bitmuster
als Zeichen, Vorzeichenzahl, vorzeichenlose
29
Name
Adresse
Inhalt
(hex)
88
87
86
ZAHL2
85
84
12
83
34
82
56
NUM
81
78
80
87
7f
65
7e
12
ZAHL1
7d
34
7c
21
7b
6f
7a
6c
79
6c
78
61
77
68
76
64
75
63
0804 8074
62
DAT 0804 8073
61
.data
DAT:
.ascii ”abcd”
.ascii ”hallo!”
ZAHL1: .word 0x1234,34661
NUM:
.long 305419896
ZAHL2: .long 439041101
Tabelle 10: Belegung des Arbeitsspeichers (oben) gemäß der
Datendefinition unten. Beachten
Sie dabei den little endian-Modus
(siehe Abschnitt 3.3.1).
Zahl, Adresse usw. anzusehen ist, ergibt sich aus dem jeweiligen Programmzusammenhang. Daten können auch ohne Namen deklariert werden. Auf sie kann
ähnlich wie bei Listen durch direkte oder indirekte Adressierung zugegriffen
werden.
6.3
Arithmetik für Adressen, Konstante und Festwerte
Für Berechnungen bei der Initialisierung von Konstanten, für Berechnungen
der Länge von Datenstrukturen und Adressen steht eine Integer-Arithmetik
zur Verfügung, die mit den Operatoren +, − und ∗ arbeitet. Die Gleichungen
werden vom Assembler ausgewertet. Im Objektcode erscheinen nur noch die
Ergebnisse. Insbesondere bedeutet dies, dass diese Arithmetik nicht bei Programmausführung möglich ist. Beispiele sind movl ZAHL1+2,%ebx oder leal
ZAHL2-4,%ebp.
6.4
Konflikte bei Datenformaten
Eine Reihe von Befehlen oder Programmiersituationen erfordern die Verwendung von ganz bestimmten Datenformaten. So können beispielsweise mit dem
Befehl add nur Summanden vom selben Datenblock addiert werden (siehe Abschnitt 7.2.2). Es können auch Situationen auftreten, in denen das Datenformat
einer verwendeten Größe nicht bekannt ist oder aus einem Datenfeld bestimmte
Blocklängen herausgegriffen werden sollen. Für solche und ähnliche Situationen
gibt es mehrere Möglichkeiten
6.4.1
Operandengrößen-Zusatz (OpSize-Suffix)
In den Assemblerbefehlen wird üblicherweise genau angegeben, welche Blocklänge
verwendet werden soll, um Konflikte mit Datenformaten oder Unklarheiten
über Datenformate adressierter Größen zu vermeiden. In der AT&T-Notation
wird bei den verwendeten Prozessorbefehlen ein Zusatz angehängt (OpSizeSuffix), der die zu behandelnde Blockgröße festlegt: l für long (4 Byte, Doppelwort), w für word (2 Byte, Wort) und b für Byte. Bei Konflikten bestimmt
diese Angabe über die auszuführende Maßnahme. Zum Beispiel wird bei dem
Befehl movw MEM,%eax nur das Register ax belegt, bei movl MEM,%ax das Register eax. Bei der Kollision von Byte und Doppelwort bzw. Wort gibt es in
einigen Fällen Fehlermeldungen. Werden die Zusätze l, w oder b weggelassen,
dann entscheidet der Registeroperand.
6.4.2
Direkte Umwandlung (Erweiterung)
Zur Umwandlung (Erweiterung) von Datenblöcken (Byte → Wort, Wort →
Doppelwort, usf.) stehen die Befehle
• cbtw (Erweiterung von al nach ax),
• cwtl (Erweiterung von ax nach eax),
• cwtd (Erweiterung von ax nach dx:ax),
• cltd (Erweiterung von eax nach edx:eax)
30
Befehl
cbtw
cbtw
vorher
movzbw %ah,%dx
movsbw %ah,%dx
ax=ah:al
???? ???? : 0111 1111
???? ???? : 1000 0000
ax
10111001 11100100
10111001 01100100
10111001 01100100
ax
0000 0000 0111 1111
1111 1111 1000 0000
dx
11000111 00001101
00000000 10111001
11111111 10111001
Tabelle 11: Beispiele für die Auflösung von Datenkonflikten durch Erweiterung
auf einen breiteren Datenblock und durch erweiterndes Kopieren
zur Verfügung. Diese werden ohne Operanden benutzt und beziehen sich nur
auf die angegebenen Register. Die Befehle stellen Abkürzungen dar, so bedeutet
cbtw convert byte to w ord. Die Erweiterungen erfolgen vorzeichenerhaltend
durch Auffüllen mit Nullen (positves Vorzeichen) oder mit Einsen (negatives
Vorzeichen). Beispiele sind in Tabelle 11 gezeigt.
6.4.3
Erweiterndes Kopieren
Für das erweiternde Kopieren op1→op2 (z. B. al→bx oder ax→eax) gibt es die
Befehle movs op1,op2 für vorzeichenerhaltende Erweiterung und movz op1,op2
für mit Nullen auffüllende Erweiterung. An die Befehle ist die Art der Erweiterung als Suffix anzuhängen: bl (byte→long), bw (byte→word), wl (word→long).
Ziel (op2) des Kopierbefehls kann nur ein Register sein. In der Intel-Notation
lauten diese Befehle movsx bzw. movzx.
Beispiel: movsbl %al,%edx erweitert al vorzeichengerecht zu einem Doppelwort
und kopiert nach edx. Weitere Beispiele sind in Tabelle 11 gezeigt.
6.5
Direktiven
Direktiven sind Anweisungen an den Assembler. Sie betreffen die Reservierung
und Vorbelegung von Arbeitsspeicher für Daten, die Zugriffsarten auf Programmteile und die weitere Verarbeitung der Objektdateien. Daneben bieten
sie dem Programmierer eine Vielzahl von Hilfen und ermöglichen die Steuerung des Assemblierungsablaufs. Die Syntax sieht vor, dass Direktiven mit einem Punkt beginnen. Der Assembler wertet die Direktiven aus und ersetzt sie
durch die ermittelten Ergebnisse. Der Abschnitt 17 behandelt die wichtigsten
Direktiven und enthält eine komplette Liste der verfügbaren Direktiven.
Viel verwendete Direktiven sind die Datendefinitionsdirektiven .ascii, .byte,
.word, .long, die Set-Direktiven .set, .equ, .equiv, die Reservierungs-Direktiven
.space, .fill, .skip, die Makro-Direktive .macro und die Include-Direktive .include.
An dieser Stelle soll auf die location counter-Direktive “.” (ein Punkt) hingewiesen werden, die den aktuellen Stand des Speicherplatz-Zählers an den
Assembler übergibt, der ihn in die betreffenden Ausdrücke einsetzt. Der location counter wird häufig benutzt, um die Länge von Bereichen durch den
Assembler ermitteln zu lassen.
Beispiel:
STR:.ascii‘‘Zeichenkette unbestimmter Länge’’
31
.set
LSTR,.-STR
Durch die Differenz .-STR wird die Differenz der aktuellen Adresse und der
Adresse STR gebildet, d. h. die Länge der bei STR beginnenden Zeichenkette
berechnet. Diese Länge ist unter dem Namen LSTR verfügbar.
32
7
Allgemeine Befehle
Informationen über die (prozessorabhängigen) Befehle des Intelprozessors sind
nicht in den Linux-Infodateien enthalten, sondern müssen der Beschreibung
des Prozessors entnommen werden [8]. Hierfür eignen sich auch die in großer
Zahl vorhandenen Bücher zum Microsoft Macro Assembler (MASM), die fast
alle die Befehle in lexikalischer Form enthalten z. B. [3][4][10]. Allerdings ist zu
berücksichtigen, dass statt der dort üblichen Intel-Schreibweise für den GNUAssembler die AT&T-Notation zu benutzen ist. Diese ist in der Broschüre The
GNU Assembler der Free Software Foundation von Dean Elsner, Jay Fenlason
& friends beschrieben [1] und als Auszug in Abschnitt 16 wiedergegeben.
7.1
Datenmanipulation
Die meistbenutzten Befehle zur Datenmanipulation sind:
movl
quelle, ziel Kopieren von quelle nach ziel mit den Regeln:
• gleiche Länge von quelle und ziel,
• erlaubt: Register ↔ Register und Register ↔
Speicher, nicht erlaubt: Speicher ↔ Speicher
xchgl op1,op2
Austausch der Inhalte von op1 und op2 mit denselben
Regeln.
leal
Adresse von op1 (m) bestimmen und in op2 (r32) speichern
op1,op2
pushl op
Ablegen von op (m16, m32, r16, r32) auf dem Stack
(siehe Abschnitt 2.2)
popl
Zurückspeichern vom Stack in op (m16, m32, r16, r32,
siehe Abschnitt 2.2)
op
pushal
(a=all, l=long) Ablegen der Register eax, ecx, edx, ebx,
esp, ebp, esi, edi bzw. ax, cx, dx, bx, sp, bp, si, di auf
dem Stack
popal
Rückspeichern vom Stack in die Register edi, esi, ebp,
esp, ebx, edx, ecx, eax bzw. di, si, bp, sp, bx, dx, cx, ax
Die Befehle sind mit dem OpSize-Suffix l (Datenblock .long) geschrieben. An
der Stelle des Suffix l ist gegebenenfalls auch eines der OpSize-Suffizes w oder
b zu setzen. Bei den Stackbefehlen push und pop sind nur die Suffices l und
w möglich (siehe Abschnitt 3.4.2). Die in Klammern gesetzten Angaben beziehen sich auf die erlaubten Speicherarten: m=Arbeitsspeicher, r=Register, z. B.
bedeutet r16: 16-bit-Register.
Ausführliche und detailierte Informationen zu diesen und zu weiteren Befehlen können Sie den Manpages entnehmen (siehe Abschnitt 4.1).
33
7.2
Arithmetikbefehle
Im Intelprozessor 80 x86 können direkt nur ganze Zahlen mit oder ohne Vorzeichen verarbeitet werden. Zur Gleitkommaarithmetik wird der mathematische Koprozessor verwendet, der als ein selbständiger Prozessor mit eigenem
Befehlssatz und eigenen Registern anzusehen ist. Die Darstellung der ganzen
Zahlen mit und ohne Vorzeichen wurde in der Lehrveranstaltung Grundlagen
der Informatik behandelt und sollte, wenn nötig, in Erinnerung zurückgerufen
werden [20].
7.2.1
Arithmetik-Flaggen
Bei arithmetischen Operationen können aus verschiedenen Gründen besondere Ereignisse auftreten. Damit sind Bereichsüberschreitungen, Vortäuschen eines Vorzeichenwechsels (overflow ), Überträge vom high bit nach außen (carry), Null-Ergebnis (zero), Negativ-Ergebnis (sign) gemeint. Sie werden durch
Veränderung von Flaggen (flags) im flag-Register gemeldet (siehe Abschnitt
8.1 und Tabelle 17). Durch bedingte Sprungbefehle können Programmverzweigungen vom Zustand der flags und damit von bestimmten Ereignissen abhängig
gemaccht werden.
• CARRY-flag
Das carry-flag (cf ) wird gesetzt, wenn bei einer Operation ein Übertrag
aus dem höchstwertigen Bit hinaus erfolgt (z.B. bei der Addition oder
Subtraktion), andernfalls wird dieses flag gelöscht. Das carry flag meldet
also fehlerhafte Ergebnisse bei Operationen mit vorzeichenlosen Zahlen.
• OVERFLOW-flag
Das overflow-flag (of ) wird gesetzt, wenn eine Operation eine Veränderung des Vorzeichenbits (high bit) verursacht. Dies geschieht, wenn einen
Übertrag in das Vorzeichenbit, aber kein Übertrag aus dem Vorzeichenbit (cf=0) oder umgekehrt, wenn durch die Operation ein Übertrag aus
dem high bit (cf=1) erfolgte, aber kein Übertrag in das Vorzeichenbit.
Andernfalls wird das overflow flag gelöscht. Das overflow flag meldet also
Fehler bei Operationen mit vorzeichenbehafteten Operanden.
• ZERO-flag
Das zero-flag (zf ) wird gesetzt, wenn das Ergebnis nach einer Operation
0 ist; andernfalls wird dieses flag gelöscht.
• SIGN-flag
Das sign-flag (sf ) wird gesetzt, wenn das höchstwertigste Bit des Ergebnisses nach einer Operation gesetzt ist; andernfalls wird dieses flag
gelöscht. Das sign-flag enthält somit eine Kopie des Vorzeichenbits.
• AUXILIARY CARRY-flag
Das auxiliary carry-flag (af ) wird gesetzt, wenn bei einer Operation ein
Übertrag von Bit 3 hinaus erfolgt; andernfalls wird dieses flag gelöscht.
af wird für die BCD-Arithmetik verwendet, bei der eine Dezimalziffer in
einem Halbbyte gespeichert wird.
34
Befehl
jc
jnc
jno
jns
jnz
jo
js
jz
Sprung ausführen, wenn
carry flag gesetzt
carry flag nicht gesetzt
overflow flag nicht gesetzt
Ergebnis positiv
Ergebnis ungleich Null
overflow flag gesetzt
Ergebnis negativ
Ergebnis Null
Bedingung
cf=1
cf=0
of=0
sf=0
zf=0
of=1
sf=1
zf=1
Tabelle 12: Flaggengesteuerte Sprungbefehle und deren Bedeutung. Der Buchstabe j steht jeweils für jump. Die nachfolgenden Buchstaben beziehen sich auf
das betrachtete flag (z. B. jz: verzweige, wenn das zero flaggesetzt ist (zf=1)
oder jno: verzweige, wenn das overflow flag nicht gesetzt ist (of=0).
Um Fehler bei Arithmetikoperationen zu erfassen, müssen die passenden
flags nach der Rechenoperation abgefragt werden. Das bedeutet, dass bei Operationen mit vorzeichenlosen Zahlen (unsigned integer) das carry flag, bei vorzeichenbehafteten Zahlen (signed integer) das overflow flag erfolgreiche oder
fehlerhafte Aktionen anzeigen. Das Vorgehen ist im folgenden Beispiel für vorzeichenlose Zahlen verdeutlicht.
addl
jc
movl
%ebx,%eax # ebx+eax → eax
ERR1
%eax,Z1
Die direkt auf den Additionsbefehl folgende Zeile (jc= jump if cf=1 (cf
gesetzt)) führt im Fehlerfalle eine Verzweigung zum Label ERR1 aus, wo eine Fehlerbehandlung zu erfolgen hat. Wenn die Bedingung cf=1 nicht erfüllt
ist, wird mit der nächsten Zeile (kopiere eax nach Z1) fortgefahren. Weitere
Verzweigungsmöglichkeiten sind in Tabelle 12 aufgeführt.
7.2.2
Addition, Subtraktion
Für die Addition und Subtraktion von Festkommazahlen wird nicht zwischen
Zahlen mit und ohne Vorzeichen unterschieden. Dies liegt in der Darstellung negativer Zahlen durch das Zweier-Komplement begründet. Die folgenden AssemblerBefehle gelten, wobei an Stelle des Opcode-Suffix l auch b oder w stehen kann.
addl
subl
op1,op2
op1,op2
# op1+op2 → op2
# op2-op1 → op2
Für die Operanden op1 und op2 gelten diese Einschränkungen:
• Die Operanden sind entweder Byte-, Wort- oder Doppelwortgrößen.
• Die Operanden sind vom gleichen Datenblock, sonst ist eine Konvertierung erforderlich (siehe Abschnitt 6.4).
• Nur ein Operand darf eine Speichervariable sein.
35
Beispiel: Addition von 64-Bit-Quadwörtern. Tabelle 13 zeigt
die im .data-Segment definierten und initialisierten Quadwörter.
Beachten Sie, dass die Speicherung der Zahlen im little-endianModus erfolgt. Da der Prozessor ’nur’ über Additionsbefehle für
maximal 32-Bit-Zahlen verfügt, wird die Addition in 2 Schritten durchgeführt, wobei der eventuell auftretende Übertrag durch
den Befehl adcl (’Additiere unter Einbezug des Übertrags aus
der vorangehenden Addition’) erfasst wird. Die Addition der
Doppelwort-Teile der Quadwörter wird also von ’rechts nach links’
vorgenommen.
Q1:
Q2:
.data
.quad
.quad
.text
. . . .
movl
addl
movl
adcl
. . . .
0x2c4f3ee876aa4520
0x773ac9021f3daaaa
Q2,%eax
%eax,Q1
Q2+4,%eax
%eax,Q1+4
ffff..
Q2+7
Q2+6
Q2+5
Q2+4
Q2+3
Q2+2
Q2+1
Q2
Q1+7
Q1+6
Q1+5
Q1+4
Q1+3
Q1+2
Q1+1
Q1
77
3a
c9
02
1f
3d
aa
aa
2c
4f
3e
e8
76
aa
45
20
0000..
Aufgabe: Wie geht die Subtraktion Q2 − Q1 ?
Vielfach werden auch die folgenden Arithmetik-Befehle benutzt:
• Inkrementieren (Erhöhen um 1) incl op
Tabelle 13: Addition von
Quadwörtern (8Byte)
• Dekrementieren (Erniedrigen um 1) decl op
• 2er-Komplement (Vorzeichenwechsel bei ganzen Zahlen)
negl op
• 1er-Komplement not op
7.2.3
Multiplikation und Division
Eine Übersicht über die möglichen Multiplikations- und Divisionsbefehle wird
in den Tabellen 14, 15 und 16 gegeben.
Auf den ersten Blick ist es vielleicht verwunderlich, dass ein Großteil der
Multiplikationsbefehle und alle Divisionsbefehle nur einen Operanden benutzen. Die Operanden sind der Multiplikator (Tabelle 14) und der Divisor (Tabelle
15). Als zweiter, im Befehl nicht geschriebene Operand werden das Register eax
und der Registerverbund eax:edx verwendet. Da daran nichts geändert werden
kann, unterbleibt die Angabe in den Befehlen. Die genauen Regelungen sind in
den Tabellen 14 und 15 aufgeführt.
Bei Multiplikation und Division gibt es unterschiedliche Befehle für Zahlen
mit und ohne Vorzeichen. mul bezieht sich auf die Multiplikation von Zahlen ohne Vorzeichen, imul auf die Multiplikation von Zahlen mit Vorzeichen.
Entsprechendes gilt für die Division.
Der Befehl imul für Vorzeichenzahlen existiert zusätzlich zur Ein-OperandenForm in einer Zwei-Operanden-Form (imul op1,op2 mit op1 · op2 → op2) und
36
Befehl
mulb
mulw
mull
imulb
imulw
imull
imulw
imull
imulw
imull
Multiplikand
op unsigned
op unsigned
op unsigned
op signed
op signed
op signed
op1,op2
op1,op2
op1,op2,op3
op1,op2,op3
al
ax
eax
al
ax
eax
op1
op1
op1
op1
(r16/m16/i)
(r32/m32/i)
(i)
(i)
Multiplikator
(Operand)
r8 oder m8
r16 oder m16
r32 oder m32
r8 oder m8
r16 oder m16
r32 oder m32
op2 (r16)
op2 (r32)
op2 (r16/m16)
op2 (r32/m32)
→ Produkt
→
→
→
→
→
→
→
→
→
→
ax
dx:ax
edx:eax
ax
dx:ax
edx:eax
op2 (r16)
op2 (r32)
op3 (r16)
op3 (r32)
Tabelle 14: Multiplikation von ganzen Zahlen für vorzeichenlose Zahlen
(op unsigned) und vorzeichenbehaftete Zahlen (op signed). op1, op2, op3 sind
sämtlich Vorzeichenzahlen. Zu den Bezeichnungen in der Tabelle: r32 bedeutet
32-Bit-Register, m16 16-bit Speichergröße (m von memory), i Festwert (i von
immediate)
Befehl
divb
divw
divl
idivb
idivw
idivl
Dividend
op
op
op
op
op
op
unsigned
unsigned
unsigned
signed
signed
signed
ax
dx:ax
edx:eax
ax
dx:ax
edx:eax
Divisor
(Operand)
r8 oder m8
r16 oder m16
r32 oder m32
r8 oder m8
r16 oder m16
r32 oder m32
→
Quotient
Rest
al
ax
eax
al
ax
eax
ah
dx
edx
ah
dx
edx
Tabelle 15: Division von ganzen Zahlen für vorzeichenlose Zahlen (op unsigned)
und vorzeichenbehaftete Zahlen (op signed)
einer Drei-Operanden-Form (imul op1,op2,op3 mit op1 · op2 → op3). Im Gegensatz zur Ein-Operanden-Form sind bei diesen Befehlen auch Festwerte als
Multiplikatoren zugelassen.
Wenn bei Multiplikationen der high-Teil des Ergebnisses nicht 0 ist, werden
die flags cf und of gesetzt. Umgekehrt werden die flags cf und of gelöscht, wenn
das Ergebnis in al (Byte mal Byte), in ax (Wort mal Wort) oder in eax (Doppelwort mal Doppelwort) passt, die high-Teile also nicht benötigt werden. Bei
der Division wird bei Ergebnisüberlauf keine flag gesetzt. In diesem Falle wird
der Interrupt 00hex ausgelöst. Dies bewirkt eine Anzeige (z. B. division overflow) und die Beendigung des Programms. Ergebnisüberlauf tritt bei Division
(mit div) durch 0 oder 1 und bei Zerstörung des Vorzeichens (mit idiv) auf.
Deswegen ist dringend eine Überprüfung auf Ergebnisüberlauf zu empfehlen.
Bei den Operationen in Tabelle 14 und Tabelle 15 verhalten sich die Register
edx und eax wie ein 64-Bit-Register. Dabei stellen edx den higher und eax den
lower part dar.
In den folgenden Beispielszeilen sollen die Variablen ZL, ZW und ZB 32Bit-, 16-Bit und 8-Bit-Speichervariable darstellen.
37
Dividend /
+
+
-
Divisor (op)
+
+
-
→
Quotient
+
+
und Rest
+
+
+
-
Tabelle 16: Vorzeichenregelung bei der Division von ganzen Zahlen
mull
%ebx
mull
imull
imull
divl
idivw
ZW
$10,%ecx
$0xa,ZL,%eax
ZL
%bx
#
#
#
#
#
#
#
Inhalt von eax wird mit ebx multipliziert,
Ergebnis steht in edx:eax
ZW mal ax → dx:ax
10 mal ecx → ecx
10 mal ZL → eax
edx:eax durch ZL → eax, Rest in edx
dx:ax durch bx → ax, Rest in dx
38
8
Programmsteuerung
8.1
Labels und flag-Register
Die Programmsteuerung geschieht auf Assemblerebene durch Sprünge zu Labels. Die Sprünge können ohne oder mit Bedingung erfolgen. Bedingte Sprünge
werden durch die flags des 32-Bit-flag-Registers eflag gesteuert. Die Bits 0 bis
15 des flag-Registers sind in Tabelle 17 erläutert. Die Status-flags des flagRegisters werden in bestimmten Programmsituationen gesetzt und gelöscht.
Sprünge können von deren Zustand abhängig gemacht werden. flags werden in
der Regel nur ausgewertet (gelesen), aber nicht direkt verändert. Eine Ausnahme bilden das carry flag, das direction flag und das interrupt flag. Sie werden
mit stc, std, sti gesetzt und mit clc, cld cli gelöscht.
In Abschnitt 7.2.1 sind die flags cf, of, zf, sf und af erläutert, die zur
Kontrolle der arithmetischen Operationen dienen Im folgenden wird auf weitere
flags eingegangen.
• PARITY-flag
Das parity-flag (pf) wird gesetzt, wenn nach einer Operation die Anzahl
der Bits 1 im low-Byte des Ergebnisses geradzahlig ist, andernfalls wird
dieses flag gelöscht.
• DIRECTION-flag
Das direction-flag (df ) findet bei der Verarbeitung von Zeichenketten
(Strings) Verwendung (siehe Abschnitt 12). Wird df gesetzt, so werden
Strings von hinten nach vorne (d. h. mit absteigender Adresse), wird df
gelöscht, von vorne nach hinter durchlaufen.
• INTERRUPT ENABLE-flag
Das interrupt enable-flag (if ) findet bei der Interruptverarbeitung Verwendung. Wird if gesetzt, so können externe maskierbare Interrupts das
momentan laufende Programm unterbrechen. Wird if gelöscht, ist dies
nicht möglich.
• TRAP-flag
Das trap-flag (tf ) wird zum Ein-/Ausschalten des Single-Step-Modus verwendet. Wird das Trap-flag gesetzt, so erzeugt der Prozessor nach jedem
Befehl einen Single-Step-Interrupt und verzweigt in eine spezielle Interruptroutine (z.B. in einen Monitor oder ein Debugger-Programm). Wird
das Trapflag gelöscht, so ist der Single-Step-Modus wieder aufgehoben.
Dieses flag wird z. B. von Debugging-Programmen benutzt.
8.2
Sprünge
Sprungbefehle bestehen aus einem Teil, der den Maschinenbefehl des gewünschten Sprungtyps enthält, und einem Zusatz, der die Sprungweite (displacement)
angibt. Der GNU-Assembler optimiert den Platzbedarf insoweit, dass - wenn
möglich - ein Byte-Displacement benutzt wird (Sprünge um bis zu -128 bzw.
+127 Programmschritte bezogen auf die Position des Sprungbefehls). Anderenfalls wird ein Doppelwort benutzt (bis zu -2 GByte bzw. +2 GByte Programmschritte, 32 Bit-Displacements). 16 Bit-Displacements werden nicht unterstützt.
39
Bit
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
flags
of
df
if
tf
sf
zf
af
pf
cf
Typ
Unused
Protected mode
Protected mode
Protected mode
Status
Control
Control
Control
Status
Status
Unused
Status
Unused
Status
Unused
Status
Name
Erläutert in
Overflow
Direction
Interrupt
Trap
Sign
Zero
Abschnitt
Abschnitt
Abschnitt
Abschnitt
Abschnitt
Abschnitt
Aux. Carry
Abschnitt 7.2.1
Parity
Abschnitt 8.1
Carry
Abschnitt 7.2.1
7.2.1
8.1
8.1
8.1
7.2.1
7.2.1
Tabelle 17: Die 16 unteren Elemente des 32-Bit-flag-Registers und deren Bedeutung
Zu bemerken ist, dass die Sprungbefehle jcxz, jecxz, loop, loopz, loope, loopnz
und loopne nur mit Byte-Displacements benutzt werden dürfen. Diese Sprungbefehle sollten also nur dann benutzt werden, wenn sicher ist, dass die Sprungweite unter -128 bzw. +127 ist (Die C-Compiler gcc und g++ benutzt diese
Befehle nicht).
8.2.1
Unbedingter Sprung
MRK:
. . . .
jmp
8.2.2
MRK
# Sprung zur Marke MRK
Bedingter Sprung
Die Sprungbedingung für den bedingten Sprung kann direkt aus einer Statusprüfung (direkte Steuerung durch flags) oder aus einem vorhergehenden
Vergleich abgeleitet werden. Im ersten Fall kann ein Sprung vom Wert eines
flag-Bits abhängig gemacht werden. Damit kann auf Besonderheiten reagiert
werden, die bei bestimmten Operationen (z. B. Überläufe oder Überträge bei
Arithmetikoperationen) durch Setzen oder Löschen von flag-Bits angezeigt werden. Zu diesem Zweck sind in den Listen der Prozessorbefehle (z. B. [3] oder
andere) die möglichen Änderungen des flag-Registers bei Ausführung des jeweiligen Befehls aufgeführt.
addl
jc
%edx,%ebx
FEHLER
# Addition
# Sprung nach FEHLER, wenn bei der
40
Befehl
ja
jae
jb
jbe
jc
jcxz
jecxz
je
jg
jge
jl
jle
jna
jnae
jnb
jnbe
jnc
jne
jng
jnge
jnl
jnle
jno
jnp
jns
jnz
jo
jp
jpe
jpo
js
jz
Sprung ausführen, wenn
größer
größer oder gleich
kleiner
kleiner oder gleich
carry=1
cx-register=0
ecx-register=0
gleich
größer
größer oder gleich
kleiner
kleiner oder gleich
nicht größer
nicht größer oder gleich
nicht kleiner
nicht kleiner oder gleich
kein Übertrag
nicht gleich
nicht größer
nicht größer oder gleich
nicht kleiner
nicht kleiner oder gleich
kein Überlauf
Parität ungerade
positiv
ungleich
Überlauf
Parität gerade
Parität gerade
Parität ungerade
negativ
null
Bedingung
cf=0 and zf=0
cf=0
cf=1
cf=1 or zf=1
cf=1
cx-register=0
ecx-reg.=0
zf=1
sf=of and zf=0
sf=of
sfeqof
zf=1 or sfeqof
cf=1 or zf=1
cf= l
cf=0
cf=0 and zf=0
cf=0
zf=0
zf=1 or sfeqof
sfeqof
sf=of
zf=0 and sf=of
of=0
pf=0
sf=0
zf=0
of=1
pf=1
pf=1
pf=o
sf=1
zf=1
Bemerkungen
gleich wie jnbe
gleich wie jnb
gleich wie jnae
gleich wie jna
ab 80386
gleich wie
gleich wie
gleich wie
gleich wie
gleich wie
gleich wie
gleich wie
gleich wie
gleich wie
gleich wie
jz
jnle
jnl
jnge
jng
jbe
jb
jae
ja
jaf
gleich
gleich
gleich
gleich
jle
jl
jge
jg
wie
wie
wie
wie
gleich wie jnp
Tabelle 18: Sprungbefehle und deren Bedeutung. Man beachte, dass die Bezeichnungen g (greater) und l (less) für vorzeichenbehaftete Zahlen, die gleichbedeutenden Bezeichnungen a (above) and b (below) für vorzeichenlose Zahlen
verwendet werden.
41
movl
%ebx,RS
# Addition ein Übertrag auftrat
# siehe Abschnitt 7.2.1
# Fortsetzung,wenn fehlerfrei
. . . .
FEHLER:
# Beginn der Fehlerbehandlung
Häufig wird die Sprungbedingung aus einem Vergleich abgeleitet. Der Sprungbefehl kann dann als Bedingungssatz interpretiert werden:
cmpl
je
$0,%eax
LB
# Vergleiche eax mit 0
# Springe zur Marke LB, wenn
# gleich (je = jump if equal)
Der cmp-Befehl arbeitet ganz ähnlich dem sub-Befehl (siehe Abschnitt 7.2.2).
Er bildet die Differenz der Operanden (Zweiter minus Erster), speichert sie aber
im Gegensatz zum sub-Befehl nicht. Wie bei der Subtraktion werden aber die
entsprechenden flags gesetzt. Für den cmp-Befehl gilt:
• cmpl op1,op2 berechnet op2-op1 und setzt entsprechend zf, cf, of, sf.
• op1 und op2 können Register, Festwerte (op1) und Speichergrößen sein.
• Größen mit und ohne Vorzeichen dürfen nicht gemischt werden
8.3
8.3.1
Schleifen
Fuß- und kopfgesteuerte Schleifen, Verzweigungen
Der Aufbau von Schleifen muss vom Programierer mit Hilfe elementarer Befehle
gestaltet werden. Elemente der Schleife sind:
• Initialisierung eines Indexregisters (häufig esi) als Laufvariable
• Festlegung eines Labels für den Schleifenanfang
• Erhöhung oder Erniedrigung des Indexregisters
• Vergleich des Indexregisters mit der Zielgröße und
◦ Sprung zum Schleifenanfang, wenn die Zielgröße nicht erreicht ist
oder
◦ Verlassen der Schleife, wenn die Zielgröße erreicht ist
Fuß- und kopfgesteuerte Schleifen (Repeat- bzw. While-Schleifen), Verzweigungen (If-Then-Else, Case- bzw. Switch-Strukturen) unterscheiden sich durch
die Position, an der der Vergleichsbefehl (cmp) und der Befehl für den bedingten
Sprung (je, jnge, ...) auftreten. Am Beispiel einer kopfgesteuerten Schleife
soll die Schleifenkonstruktion exemplarisch erläutert werden.
/* Festlegung der Anzahl der Durchläufe N D*/
movl
N D, %ecx
/* Initialisierung des Schleifenzählers esi */
movl
$0,%esi
42
/* Kopfsteuerung: Abfrage, ob der Schleifenzähler */
/* das Schleifenende erreicht hat */
SCHL:
cmpl
%ecx,%esi
jge
AUS
Schleifenkörper
jmp
SCHL
AUS:
Programmfortsetzung
Für die anderen genannten oder weitere Strukturen wie z. B. in einander
verschachtelte Schleifen muss ein Ablaufdiagramm erstellt werden, das dann in
Assemblerbefehle umgesetzt wird.
8.3.2
loop-Schleifen
Der loop-Befehl ist eine vereinfachte Form der Schleifenbildung und
kann nur für einfache, unverschachtelte Schleifen verwendet werden.
loop benutzt das Register ecx als
Schleifenzähler. Das Register ecx
wird mit der gewünschten Zahl von
ecx
Schleifenkörper
dekrementieren
Durchläufen initialisiert. Der Befehl
loop dekrementiert den Inhalt des
Register ecx und prüft dann auf den
Wert 0. Wenn ecx ungleich Null ist,
ecx = 0 ?
so wird ecx um 1 erniedrigt und ein
Sprung zur angegebenen Marke (i.
a. Schleifenanfang) durchgeführt. Ist
ecx Null, wird der nachfolgende Befehl ausgeführt (siehe Abbildung 6).
Da der Befehl loop den Inhalt
von ecx lediglich auf die Bedingung
ecx = 0 und nicht auf ecx 5 0 Abbildung 6: Ablaufplan der loopüberprüft, ergeben sich dann Endlos- Schleife
schleifen, wenn ecx mit 0 oder einem
negativen Wert initialisiert wurde.
Struktur der loop-Schleife:
movl
N D,%ecx
# Register ecx (Zähler) enthält
# die Anzahl der Durchläufe N D
ABC:
Schleifenkörper
loop
ABC
# loop dekrementiert ecx um 1,
# bei ecx > 0 Sprung zum Label ABC
# bei ecx = 0 Verlassen der Schleife
43
Die loop-Schleife verwendet als einziges Abbruchkriterium den Inhalt des
Zählers ecx. Die Schleifen mit loope oder loopne verfügen in Kombination mit
dem cmp-Befehl (e = equal, ne = not equal) über ein zusätzliches Abbruchkriterium.
cmpl
loopne
WERT1,WERT2
ABC
# Sprung zum Label abc wenn
# ecx>0 und(∧) WERT16=WERT2 (d.h. zf=0)
Die Schleife wird verlassen wenn ecx Null wurde oder(∨) wenn die verglichenen Werte nicht gleich sind. Die Befehle loopne bzw. loope können auch als
loopnz bzw. loopz geschrieben werden.
Beispiel:
/* Suche in einer Liste von 100 Wörtern nach dem Wert -1 */
/* Im Datensegment: TABLE: .space 200 */
movl
$-2,%esi
movl
$100,%ecx
leal
TABLE,%ebp
L2:
addl
$2,%esi
cmpw
$-1,(%ebp,%esi)
loopne L2
44
9
Prozeduren
Ein Programm sollte nicht als ein einziger kompakter Block angelegt sein, sondern in mehrere (so viele wie nur möglich) kleine, möglichst unabhängige Module zerlegt werden, die jeweils überschaubare Aufgaben übernehmen. Damit
kann erreicht werden:
• Übersichtlichkeit, Lesbarkeit, Veränderbarkeit
• Sparsamer Umgang mit Arbeitsspeicher
• Teilaufgaben, die mehrfach benutzt werden, liegen nur einmal im Arbeitsspeicher vor.
• Verwendung von Programmeinheiten in anderen Programmen
Ein guter Programmierstil, um den Sie sich bemühen sollten, beinhaltet
folgende Regeln:
• Ein Programm sollte in Prozeduren zerlegt werden.
• Jede Prozedur führt nur eine Funktion aus.
• Prozeduren sollten klein gehalten werden.
Aber:
• Die Zerlegung sollte sinnvoll sein, da der Overhead von Prozeduren (Parameter übergeben, Datensicherung, Sprünge, ect. ) ziemlich aufwändig
ist.
9.1
Prozedur und Programm
Die ersten Assemblerprogramme, die wir erstellten, bestanden aus einer einzigen Prozedur, deren Programmcodeteil mit der Marke start: begann. Wenn
die ausführbare Datei aus mehreren Prozeduren besteht, wird im allgemeinen
die Prozedur, die vom Betriebssystem aus gestartet wird, als Programm bezeichnet. Der Aufbau dieses Programms unterscheidet sich im Prinzip nicht
von dem einer Prozedur. Im Programm muss lediglich für die Schnittstellen
zum Betriebssystem gesorgt werden. Diese Schnittstellen sind die
• Anfangsadresse start:
Bei dieser Adresse startet das Betriebssystem die Abarbeitung der Programmzeilen. Mit der Direktive .globl start wird die Anfangsadresse an
den Linker weitergegeben.
• und die Beendigungsbefehle mit dem Aufruf des SystemCalls exit
Damit wird das Programm beendet und die Regie an das Betriebssystem
zurückgegeben.
Bei einem in Prozeduren strukturierten Assemblerprogramms können die
Prozeduren vor oder hinter dem Programm angeordnet sein. Dies zeigt das
folgende Programmmodell:
45
.text
/* Beginn der Prozedur 1 */
PROZ1:
.......
ret
/* Ende der Prozedur 1 */
/* Beginn der Prozedur 2 */
PROZ2:
.......
ret
/* Ende der Prozedur 2 */
/* Beginn des Hauptprogramms */
start:
.globl start
.......
movl $1,%eax
int $0×80
/* Ende des Hauptprogramms */
/* Beginn der Prozedur 3 */
PROZ3:
.......
ret
/* Ende der Prozedur 3 */
Prozeduren, die von mehreren Programmen oder Programmierern benutzt
werden können - das sollte der Regelfall sein -, werden als separate Dateien
formuliert, getrennt assembliert und beim Linken zu den anderen Objektdateien
dazugebunden (siehe Abschnitt 4.3). Eine elegantere Lösung besteht darin, die
Prozeduren in einer oder mehereren Bibliotheken zusammenzufassen und beim
Binden die entsprechenden Bibliotheken anzugeben (siehe 9.10).
9.2
Aufbau einer Prozedur
Das Mustergerüst einer Prozedur mit dem Namen (Adresse) PROZ1 hat folgendes Aussehen.
#-----------------------------------------------------------# Name: PROZ1
# Zweck:
# Eingabe:
# Ausgabe:
# Prozeduren:
#-----------------------------------------------------------PROZ1:
/* Sicherung des Registers ebp */
pushl
%ebp
/* den aktuellen Stackpointer (esp) in ebp für den späteren */
/* Zugriff auf Übergabeparameter fixieren */
movl
%esp, %ebp
/* ggfs. Reservierung von lokalem Speicher, hier 100 Byte */
subl
$100,%esp
/* Sicherung der Register */
pushal
/* - - Prozeduranweisungen - - */
/* Register zurückspeichern */
popal
46
/* den lokalen Speicher auflassen */
addl
$100,%esp
/* Register ebp zurückspeichern */
popl
%ebp
/* Rückkehr zur rufenden Prozedur*/
ret
#-----------------------------------------------------------Die meisten Zeilen sind aus Abschnitt 5.4 bekannt. Dort ist auch die Problematik dargestellt. Die Befehle pushal und popal dienen zum Sichern der
Registerinhalte der rufenden Prozedur, damit nach dem Rückspeichern mit
popal die rufende Prozedur in der Umgebung fortgesetzt werden kann, in der
sie verlassen wurde. Wenn nur wenige Register in der gerufenen Prozedur benutzt werden, kann auch auf andere Weise gesichert werden (pushl, popl).
Lokaler Speicher (lokale Variable) wird dynamisch auf dem Stack angelegt. Er
wird nach Beendigung der Prozedur freigegeben.
Wichtig ist eine ausreichende Beschreibung der Prozedur im Kommentarkopf. Die Beschreibung muss mindestens
• eine kurze Darstellung des Zwecks der Prozedur,
• den Namen der Prozedur,
• den Namen der Datei,
• die Eingabewerte und deren Reihenfolge,
• die Rückgabewerte und deren Reihenfolge
• verwendete Prozeduren
enthalten.
9.3
Aufruf
Der Aufruf einer Prozedur erfolgt durch den Befehl call zusammen mit der
Anfangsadresse der Prozedur. Die Adresse wird im allgemeinen durch den Prozedurnamen angegeben. Auch kann jede andere in Abschnitt 5 besprochene
Adressierungsart, also auch indirekte oder indizierte Adressierung verwendet
werden, da Assembler und Linker dafür sorgen, dass in der ausführbaren Datei
eine physikalische Adresse an die Stelle der mnemonisch codierten Adressen
steht:
call
call
ADRESSE
PRZ1
oder auch in Verbindung mit Tabelle 19:
call
call
(%ebp)
(%ebp,%esi)
Der Befehl call führt zwei Aktionen aus:
47
• Die Adresse, die im rufenden Programm auf call folgt (=Rücksprungadresse) wird wird auf dem Stack abgelegt. Sie belegt dort als 32-bitAdresse 1 Doppelwort.
• Die rufende Prozedur verzweigt zur angegebenen Prozeduradresse und
beginnt dort mit der Abarbeitung der Befehle.
Eine weitergehende Sicherung von Registerinhalten, flags oder anderem erfolgt nicht, sondern wird dem Programmierer überlassen. Ebenso muss der
Programmierer dafür Sorge tragen, dass bei Beendigung der Prozedur auf die
Rücksprungadresse zugegriffen werden kann.
Bei indirekter oder indizierter Adressierung wird die Adresse der
gerufenen Prozedur einem Register entnommen. Dies kann dann
verwendet werden, wenn die Adresse der aufgerufenen Prozedur
.text
erst bei Programmablauf bestimmt wird.
ADR1: .long
Im Beispiel der Tabelle 19 werden die Adressen (Namen) der ADR2: .long
Prozeduren PRZA und PRZB auf den Speicherplätzen mit den start:
Adressen ADR1 und ADR2 abgelegt. Die Adresse von ADR1 wird # Aufruf
mit leal in ebp gespeichert. Mit der indirekten Adressierung (%ebp)
. . .
wird der Inhalt des Speicherplatzes angesprochen, dessen Adresse in
movl
ebp steht, das ist die Adresse der Prozedur PRZA. Durch Erhöhung
leal
von ebp um 4 steht die Adresse von ADR2 in ebp, so dass beim SCHL:
Aufruf mit call die Prozedur PRZB aufgerufen wird. Diese Art des
call
Aufrufs von Prozeduren kann dazu benutzt werden, um in Schleifen
addl
Prozeduren aufzurufen, die an beliebigen Stellen im Textsegment
loop
stehen.
. . .
9.4
9.4.1
Parameterübergabe
$2,%ecx
ADR1,%ebp
(%ebp)
$4,%ebp
SCHL
Tabelle 19: Aufruf der
Prozeduren PRZA und
PRZB in einer Schleife
mit indirekter Adressierung.
Grundsätzliches
Prozeduren müssen so angelegt sein, dass sie von der rufenden Prozedur unabhängig sind. Die Übergabe von Parametern muss über
Speicherbereiche geschehen, die beiden beteiligten Prozeduren a
priori zur Verfügung stehen. Diese Bedingung erfüllen die Register und der
Stack. Als Übergabegrößen werden die Werte der Parameter (“call by value”)
oder die Adressen der Parameter (“call by reference”) verwendet. Der Unterschied dieser beiden Übergabearten wird als aus Programmieren 1 bekannt
vorausgesetzt.
9.4.2
PRZA
PRZB
Register
Wenn die Anzahl der Parameter nicht zu groß ist, können Register zur Parameterübergabe benutzt werden. Bei Compilern höherer Programmiersprachen
(Pascal, C, ... ) wird bei Funktionen ein Register zur Übergabe des Funktionsergebnisses verwendet (siehe Tabelle 26). Als weiteres Beispiel können die
SystemCalls (siehe Tabelle 33) genannt werden, bei denen die Parameterübergabe ausschließlich über die Register eax, ebx, ecx, edx stattfindet.
48
9.4.3
Stack
Bei Auftreten einer größeren Zahl von Parametern oder um eine unbegrenzte
Zahl von Parametern zu ermöglichen, wird der Stack zu Übergabe benutzt. Dies
gilt insbesondere für die Kompilierung von Prozeduren bei höheren Programmiersprachen, bei denen ein generell anwendbares Verfahren benötigt wird.
Das diesbezügliche Vorgehen wurde im Zusammenhang mit der indirekten
Adressierung in Abschnitt 5.4 ausführlich dargestellt.
Häufig werden die Rückgabeparameter (sekundäre Parameter) über die
nicht mehr benötigten primären Parameter geschrieben. Wenn die Anzahl der
primären und sekundären Parameter nicht gleich sind, können zwei Fälle unterschieden werden.
1. Es gibt mehr primäre als sekundäre Parameter. Dann müssen die überzähligen Speicherplätze auf dem Stack bei oder nach Beendigung der Prozedur
freigegeben werden. Dies geschieht am einfachsten durch einen Operanden der ret-Befehl. Mit ret $n werden n Byte vom Stack entfernt.
2. Die sekundären Parameter überwiegen. In diesem Fall muss der benötigte
Platz auf dem Stack vor Aufruf der Prozedur reserviert werden. Dazu
kann der Befehl subl $n,%esp benutzt werden, der den Stackpointer um
n Bytes nach unten verschiebt und damit n Bytes reserviert.
9.5
Lokale Variable
In der Prozedur verwendete lokale Variable werden ebenso wie die Parameter
auf dem Stack realisiert. Dies geschieht durch Verschieben des Stackpointers.
Damit wird eine bestimmte Anzahl von Bytes dem Zugriff durch pushl entzogen
und kann durch indirekte oder indizierte Adressierung angesprochen werden.
Der so reservierte Bereich wird als “Stackframe” der Prozedur bezeichnet (siehe
Beispiel in Abschnitt 9.2).
9.6
Rücksprung
Der Rücksprung zum rufenden Programm erfolgt mit dem Befehl ret. Bei
Auftreten dieses Befehls laufen folgende Schritte ab:
• Von der aktuellen Position des Stackpointers aus wird ein Doppelwort
als Rücksprungadresse vom Stack in das Instruction Pointer-Register eip
gebracht. Dazu ist eine vorhergehende Stackbereinigung erfoderlich, d. h.
sämtlich Stackbelegungen in der gerufenen Prozedur müssen rückgängig
gemacht sein.
• Sprung zur Rücksprungadresse und Fortsetzen der rufenden Prozedur an
dieser Stelle.
9.7
Beispiel
In einem Beispiel sollen die geschilderten Zusammenhänge deutlich veranschaulicht werden. Das rufende Programm habe 2 Parameter (Doppelwörter) VALA
und VALB mit
pushl VALA und
49
pushl VALB
in der genannten Reihenfolge auf dem Stack gespeichert. Die Prozedur SUBR
ermittelt aus den Parametern die Summe als Ergebniswert, der zurückgegeben
wird. Für die Berechnung benötigt die Prozedur aus nicht näher erläuterten
Gründen 1 Doppelwort, 1 Wort und 1 Byte als lokale Variablen. Die lokalen
Variablen sollen dynamisch angelegt werden.
SUBR:
.globl
SUBR
/* Sicherung von ebp */
pushl
%ebp
/* Festhalten des aktuellen Werts von esp in ebp */
movl
%esp,%ebp
/* Verschieben des Stackpointers um 7 Byte nach unten, damit
Reservieren von 7 Byte (je 1x long, word, byte) als Stackframe */
subl
$7,%esp
/* Sicherung der Register */
pushal
/* Prozedurkörper */
/* Die Parameter werden vom Stack geholt */
movl
12(%ebp),%eax
movl
8(%ebp),%ebx
/* Summe */
addl
%eax,%ebx
/* Ergebnis auf den Stack */
movl
%ebx,12(%ebp)
/* Rückspeicherung (Restaurierung) der Register */
popal
/* Aufgeben des Stackframes */
movl
%ebp,%esp
/* Restaurieren von ebp */
popl
%ebp
/* Rücksprung ins rufende Programm und Auflassen von 4 Byte auf
dem Stack (ursprünglicher Platz des Parameters VALB) */
ret
$4
9.8
Makros
Ein Makro ist eine Zusammenfassung von Anweisungszeilen unter einem Namen, dem Macronamen. Ein Makro kann beim Erstellen der Quelldatei benutzt
werden, wenn ein Satz von Programmzeilen mehrfach vorkommt. In diesem Fall
muss bei jedem Auftreten nur der Makroname geschrieben werden. Ein zusätz-
50
licher Komfort für den Programmierer besteht darin, dass Platzhalter verwendet werden können, so dass das Makro bei der Verwendung an die aktuelle
Umgebung angepasst werden kann.
Das Makro besteht aus den Programmzeilen, dem Makrokörper, der durch
die Direktiven .macro (Makroanfang) und .endm (Makroende) eingerahmt wird.
In der Direktive .macro werden der Name des Makros und die Platzhalter genannt. Im Makrokörper muss den Platzhaltern ein “backslash” vorangestellt
werden.
Der Assembler ersetzt beim Assemblieren den Makronamen bei jedem Auftreten im Programmteil durch den Makrokörper und die Platzhalter durch die
Ausdrücke, die in der Anfangszeile genannt sind. Die Makrodefinition muss vor
dem ersten Aufruf im Programm auftreten.
Beispiel:
.macro
movl
movl
leal
movl
int
.endm
EINAUS PH1 PH2 PH3 PH4
\PH1,%eax
\PH2,%ebx
\PH3,%ecx
\PH4,%edx
$0×80
An der Stelle der Programmzeile
EINAUS
$4 $1 MSG LMSG
wird die Befehlsfolge
movl
movl
leal
movl
int
$4,%eax
$1,%ebx
MSG,%ecx
LMSG,%edx
$0×80
ins Programm eingesetzt und mit dem Programm assembliert.
9.9
Vergleich Prozedur/Macro
Prozedur:
• Geringerer Speicherbedarf, da der Programmcode nur einmal im Arbeitsspeicher steht.
• Aus höheren Programmiersprachen aufrufbar.
• Intensive Benutzung von Prozeduren ist guter Programmierstil.
Macro:
• Der Makrokörper wird bei jedem Aufruf eingefügt, d. h. derselbe Platzbedarf wie bei ausgeschriebenem Programm.
• Gewisser Komfort durch Verwendung der Platzhalter.
• Makros sind lediglich Programmierhilfen.
51
9.10
Bibliotheken
9.10.1
Prozeduren
• Bibliotheken (Archive) von Prozeduren bestehen aus Objektdateien, die
einzeln assembliert wurden.
• Die Objektdateien werden durch das Werkzeug ar in die Archivdatei eingebunden. Standardmäßig hat die Archivdatei die Form libname.a, wobei
name frei wählbar ist.
• Durch den Linker ld werden die benutzten Bibliotheksprozeduren den
Objektdateien hinzugefügt, um einen ausführbaren Modul zu erhalten.
Der Linker wählt nur die benötigten Bibliotheksprozeduren aus, wenn
diese einzeln assembliert wurden.
ld -o datei.x datei.o andere.o libname.a
• Die Erstellung und Bearbeitung der Bibliotheksdatei erfolgt mit Hilfe des
Werkzeugs ar :
ar -befehlscode archivname objektdateien
r = Erzeugung eines Archivs oder Hinzufügen bzw.
Ersetzen von Objektdateien
t = Auflisten der enthaltenen Objektdateien
d = Löschen von Objektdateien
archivname:
Standardform libname.a
objektdateien: Die Objektdateien werden durch Leerzeichen getrennt.
befehlscode:
9.10.2
Makros
Vorgefertigte Makros können in einer Textdatei (Makrobibliothek) abgelegt
und mit Hilfe der Direktive .include dateiname eingebunden werden (siehe Abschnitt 17). Ähnliche Programmierhilfen für kleinere Anwendungen sind
über die Direktiven .set, .equ , .equiv möglich.
9.11
Programmaufruf mit Parametern
Beim Start eines Programms (Prozedur main) aus der bash können Parameter ans Programm übergeben werden. Diese sind durch Leerzeichen zu trennen. Die Kommandozeile, mit der das Programm gestartet wurde, wird im Arbeitsspeicher abgelegt. Dabei sind die Elemente der Kommandozeile, also der
Programmaufruf und die Parameter, durch Nullzeichen getrennt. Die Anfangsadressen der Elemente werden von rechts beginnend auf dem Stack gespeichert.
Zuletzt wird die Anzahl der Elemente auf den Stack gebracht.
Mit der Kommandozeile
erde@linux:∼>prog1.x auf die Plätze fertig los !
wird die Zeichenkette
prog1.x∅auf∅die∅Plätze∅f ertig∅los∅!∅
52
im Arbeitspeicher abgelegt (∅ steht für Nullzeichen).
Die Adressen der ersten Bytes der Elemente der Kommandozeile (jeweils fett
gesetzt) werden beginnend mit der Adresse von ! über l, f, P, d, a bis hin zu p
auf den Stack geschrieben, danach die Zahl 7 der Elemente der Kommandozeile.
In den nachfolgenden Beispielen werden die Parameter als Zeichenketten
aufgefasst und ausgegeben. Wenn Zahlen bei Programmstart mitgegeben werden, müssen die aus Ziffern bestehenden Zeichenketten in (binär verschlüsselte)
Zahlen umgewandelt werden. In Assembler geschieht dies mit einer Abwandlung der Prozedur zur Zahleneingabe, in C mit Funktionen aus der Runtime
Library stdlib.h, z. B. atoi(), atol(), atof(), strtod().
9.12
Beispiel
Als Beispiele sind zwei Programme angegeben, ein Assemblerprogramm und
ein C-Programm. Das Assemblerprogramm benutzt die Prozedur COUTSTR,
die eine Zeichenkette ausgibt, die bei der übergebenen Adresse beginnt und
mit einer ASCII-Null endet. Das Assemblerprogramm gibt die Elemente der
Programm-Kommandozeile in jeweils neue Zeilen aus. Dabei werden durch
COUTSTR die Adressen in einer Schleife vom Stack geholt und als Anfangsadressen für die Ausgabe der Elemente benutzt.
/* Wiedergabe der Kommandozeilen-Elemente */
start:
.globl start
/* Anzahl der Nullzeichen (=Anzahl der Elemente der Kommandozeile)
vom Stack holen, in ecx speichern und ausgeben */
movl
(%esp), %ecx
call
putsigned
call
newline
/* Adresse der Zeichenketten vom Stack holen */
movl
$0, %esi
LAB:
/* Ausgabe einer Zeichenkette ab der Adresse ebp
bis zu einem Nullzeichen */
call
COUTSTR
call
newline
incl
%esi
cmpl
%ecx, %esi
jne
LAB
call
newline
movl
$1,%eax
int
$0x80
/****************************************************************/
/* Ausgabe einer Zeichenkette, die durch ASCII-0 terminiert ist */
/* Prozedurname: COUTSTR */
/* Dateiname: string null out.s */
/* Übergabeparameter: pushl Anfangsadresse */
/* Rückgaberparameter: keine */
/****************************************************************/
53
COUTSTR:
.globl COUTSTR
/* Die üblichen Präliminarien */
pushl
%ebp
movl
%esp, %ebp
pushal
/* Parameter (Adresse eines Komandozeilenelements) vom Stack */
movl
8(%ebp),%ecx
movl
$-1, %edx
/* edx wird hochgezählt, bis Nullzeichen erscheint */
STA:
incl
%edx
cmpb
$0, (%ecx,%edx)
jne
STA
/* Ausgabe */
movl
$1, %ebx
movl
$4, %eax
int
$0x80
/* Rückabwicklung */
popal
movl
%ebp, %esp
popl
%ebp
ret
$4
Für das nachfolgende C-Programm sind die aus dem Stack befindlichen
Adressen Zeiger auf char, genauer ein Array von Zeigern auf char. Dieser Array wird durch die Größe argv beschrieben (char *argv[]). Die Anzahl der
Elemente steht als int-Zahl in argc. Das Programm gibt nur die Parameter
und nicht den Programmaufruf aus.
/* Wiedergabe der Kommandozeilen-Argumente */
#include <iostream>
void main (int argc, char *argv[])
{
argv=argv+1;
for (int n=0; n<argc-1; n=n+1)
{
cout << *argv;
argv = argv + 1;
cout << " ";
}
cout << endl;
}
Aufgabe: Schreiben sie ein entsprechendes Programm in Assembler!
Das nächste Beispielprogramm wertet die Parameter als Zahlen aus. Im
Ernstfall sollte noch vor der Umwandlung der Zeichenketten zu Zahlen überprüft werden, ob eine Umwandlung möglich ist, oder eine Funktion mit Fehlermeldung verwendet werden.
54
/* Übergabe einer Zahl bei Aufruf des Programms */
/* und Ausgabe dieser Zahl */
/* Dateiname: mainpranum.cc */
#include<stdlib.h>
#include<iostream.h>
void main(int argc, char *argv[])
{
argv = argv + 1;
/* Ausgabe: Erste Zahl * 2 als Ganze Zahl */
cout << endl << "Doppelter Wert der ersten Zahl: < atoi(*argv)*2;
argv = argv + 1;
/* Ausgabe: Wurzel aus zweiter Zahl (als double) */
cout << endl << Wurzel aus der zweiten Zahl : < sqrt(atof(*argv));
cout << endl;
}
55
10
10.1
Handhabung von Dateien
Interruptverarbeitung
Der Intel 80×86/Pentium-Prozessor arbeitet im Anforderungsbetrieb, d. h. alle
auftretenden Betriebssystemfunktionenen, unvorhersehbare oder vorhersehbare Probleme, werden durch sogenannte Interrupts gesteuert. Interrupts sind
Unterbrechungen des laufenden Programms, die von der Hardware oder von
der Software (vom laufenden Programm) ausgelöst werden. Der Prozessor besitzt sogenannte Interruptleitungen, über die aufgetretene Ereignisse, die eine
Bearbeitung durch den Prozessor erfordern, dem Prozessor gemeldet werden.
Ein Ereignissignal kann auf verschiedene Weise ausgelöst werden z. B. durch
Drücken einer Tastaturtaste, durch eine Druckerstörung (z. B. Fehlen von Papier), durch die Beendigung der Datenübertragung zum Massenspeicher, durch
ein Programm, in dem durch Null geteilt wurde.
Interrupts sollen den Computer in die Lage versetzen, fast unmittelbar auf
unvorhersehbare Ereignisse, die durch Hardware verursacht werden, zu reagieren und den Erfordernissen dieser Hardware nachzukommen. Solche Interrupts,
die von Hardware ausgelöst werden heißen Hardware Interrupts oder externe
Interrupts. Interrupts, die durch den Prozessor selbst ausgelöst werden (z. B.
bei Division durch Null, werden auch Exceptions genannt.
Tritt ein Interrupt auf, so wird diese Anforderung durch eine Routine, Interrupthandler oder Interrupt Service Routine (ISR) genannt, bearbeitet. Jedem
Ereignistyp ist eine Interrupt Service Routine (ISR) zugeordnet. Die ISR werden vom Betriebssytem beim Start in den Arbeitsspeicher geladen. Ihre Adressen werden Interruptvektoren genannt und sind in der Interrupt Descriptor
Table aufgeführt.
Die vom Interrupt ausgelösten Maßnahmen ähneln sehr einem Prozeduraufruf (siehe Abschnitt 9). In Tabelle 20 sind die Schritte gegenübergestellt, die
auf einen Interrupt- bzw. Prozeduraufruf folgen.
Interruptbearbeitung
Gemeinsam
Prozeduraufruf
Unterbrechung des laufenden Programms
aktueller Prozessorstatus (flagRegister) → Stack
aktueller Programmzeiger eip → Stack
Löschen des Interrupt-flag:
Prozessor berechnet aus der
Interruptnummer die Adresse
der ISR und lädt diese in das
Register eip.
Prozessor lädt die in der callInstruktion angegebene Adresse in das Register eip.
Die ISR bzw. Prozedur
wird gestartet.
Beendigung der ISR mit iret.
Zur Programmfortsetzung werden der Programmzeiger eip
und das flag-Register vom
Stack zurückgeholt
Beendigung der Prozedur mit
ret. Zur Programmfortsetzung
wird eip vom Stack geholt.
Tabelle 20: Vergleich der Abwicklung von Interrupt- und Prozeduraufrufen
56
Interrupt-Mechanismen können auch von Programmen benutzt werden, um
vorgefertigte Prozeduren aufzurufen, die z. B. vom Betriebssystem zur Verfügung
gestellt werden. Diese Interrupts, die Programm aufgerufen werden können, heißen Software Interrupts oder interne Interrupts. Sie haben große Ähnlichkeiten
mit Prozeduren (siehe Tabelle 20).
Tritt im Programmablauf ein Interrupt auf, so muss bei einem externen
Interrupt der Interrupttyp und damit der Interruptvektor bestimmt werden.
Beim internen Interrupt wird der Interruptvektor im Aufruf angegeben. Mit
Hilfe der Interrupt Deskriptor Tabelle ermittelt der Prozessor die Adresse der
zuständigen ISR, die dann gestartet werden kann.
Das Betriebssystem LINUX stellt dem Anwender unter der Interruptnummer 80hex eine Vielzahl von Funktionen zur Verfügung. Diese Funktionen werden SystemCalls genannt. Sie sind in Abschnitt 18 namentlich aufgelistet. Nähere Informationen sind aus dem Manpages zu entnehmen. Für die SystemCalls
gilt, dass Parameter, ob Werte oder Adressen, nur in Registern (und zwar
in eax, ebx, ecx, edx) übergeben werden. Angaben über die Funktionen des
Softwareinterrupts 80hex sind den Manpages zu entnehmen. Wie die dort zu
findenden Angaben zu verwenden sind, ist in Abschnitt 18 erläutert.
10.2
Zugriff auf Dateien
Der Zugriff auf eine Datei wird mit creat oder open unter Angabe der Dateinamen eingeleitet, die Benutzung der Datei (write, read, ... ) erfolgt unter
Verwendung von Dateinummern (file descriptor, file handle).
Der Zugriff auf Dateiinhalte erfolgt in den folgenden Schritten:
• Erzeugen und Öffnen einer (nicht vorhandenen) Datei (SystemCall # 8)
oder Öffnen einer vorhandenen Datei (# 5) (siehe Tabelle 21)
• Rückgabe einer Zahl, mit der auf den Inhalt der Datei zugegriffen werden
kann (file descriptor, handle, Dateinummer)
• Zugriff auf den Dateiinhalt mit Schreiben (# 4), Lesen (# 3), Filepointer
bewegen (# 19), Schließen (# 6) ect. unter Benutzung der Dateinummern
Dateinummern werden ab 03hex vergeben. Darunter liegen Standardnummern für Standarddateien, die weder geöffnet noch geschlossen werden müssen:
00h Standard Input
01h Standard Output
02h Standard Error
10.3
Beispiele
In Tabelle 21 sind einige SystemCall-Funktionen für Erzeugen, Öffnen, Lesen, Schreiben und Schließen von Dateien, sowie Bewegen des Filepointers aufgeführt.
Die Zugriffsrechte werden bei der Erzeugung der Datei in der bei UNIX üblichen Weise (rwx-rwx-rwx) vergeben, beim Öffnen einer vorhandenen Datei wird
die Zugriffsart mit 0 = read only, 1 = write only oder 2 = read&write für die
57
Funktion
Rückgabeparameter
Eingabeparameter
Erzeugen und
Öffnen creat
Öffnen
open
Schließen
close
Schreiben
write
Lesen
read
Filepointer bewegen lseek
eax
ebx
ecx
edx
eax
ebx
ecx
edx
eax
ebx
ecx
edx
eax
ebx
ecx
edx
eax
ebx
ecx
edx
eax
ebx
ecx
edx
Erfolg
Fehler
8
Adresse des Pfads
Zugriffsrechte
Dateinummer
<0
5
Adresse des Pfads
Zugriffsart
Dateinummer
<0
6
Dateinummer
0
<0
4
Dateinummer
Pufferadresse
Pufferlänge
3
Dateinummer
Pufferadresse
Pufferlänge
19
Dateinummer
Verschiebung in Anzahl der Bytes
Bezug:
0=Anfang,
1=Aktuelle Position,
2=Ende
Anzahl der geschriebenen Bytes
<0
Anzahl der gelesenen
Bytes
<0
Pointerposition
Anfang
<0
bzgl.
Tabelle 21: SystemCall-Funktionen zum Dateienhandling
aktuelle Benutzung festgelegt. Eine mit creat erzeugte Datei wird gleichzeitig
geöffnet. Sie steht dem Eigentümer (owner) zur Benutzung offen, die Zugriffart
ergibt sich dabei aus dem Owner-Anteil der Zugriffsrechte. Bei Verwendung
des SystemCalls creat für eine existierende Datei wird diese leer angelegt. Die
ursprünglich vergebenen Zugriffsrechte bleiben erhalten.
Das folgende Beispiel soll die Verwendung der SystemCall-Funktionen erläutern,
stellt aber kein vollständiges Assemblerprogramm dar:
/* Dateiname wird eingelesen oder als Konstante initialisiert */
FILE: .asciz ’’~
/proto1.t’’
DESCR: .long
0
ZK:
.ascii ’’Dies ist der Dateiinhalt’’
LZK:
.long
.-ZK
/* Erzeugen und Öffnen der Datei proto1.t im Homeverzeichnis / */
movl
$8,%eax
# Nr. des SystemCall 8=creat
leal
FILE,%ebx # Pfad
movl
$066,%ecx # Rechte (----rw-rw-)
int
$0×80
# Interrupt 80h
movl
%eax,DESCR # File Descriptor
/* Abfrage, ob Vorgang erfolgreich */
. . . .
/* Die Zeichenkette ZK wird in die erzeugte Datei geschrieben */
58
movl
movl
leal
movl
int
/* Abfrage, ob
. . . .
/* Datei DESCR
movl
movl
int
/* Abfrage, ob
. . . .
$4,%eax
# Nr. des SystemCall 4=write
DESCR,%ebx # Benutzung der Datei Nr. DESCR
ZK,%ecx
# Anfangsadresse
LZK,%edx
# Länge der Zeichenkette
$0×80
# Interrupt 80h
Vorgang erfolgreich */
schließen */
$6,%eax
DESCR,%ebx
$0×80
Vorgang erfolgreich */
Weitere SystemCall-Funktionen sind analog zu verwenden. Die benötigten
Daten sind den entsprechenden Manpages zu entnehmen.
10.4
Fehlerbehandlung
Die Fehlerbehandlung erfolgt mit Hilfe des Funktionswertes, der bei SystemCalls im Register eax zurückgegeben wird. Falls die betrachtete Funktion fehlerhaft gearbeitet hat, lautet der Funktionswert “-1”. Es ist unbedingt zu empfehlen, den Funktionswert auf Fehler zu überprüfen, da dies die einzige Stelle
ist, an der ein fehlerhaftes Arbeiten des SystemCall gemeldet wird.
59
11
Bitmanipulation
Ein direkter Zugriff auf einzelne Bits durch Adressierung einzelner Bits ist nicht
möglich. Eine Gruppe von Prozessorbefehlen erlaubt, gezielt Bits innerhalb
eines Bytes, Wortes oder Doppelwortes zu verändern. Diese Prozessorbefehle
können in 3 Untergruppen eingeteilt werden:
1. Logischen Funktionen, die Bitsequenzen unter Benutzung von Bitmasken
verändern,
2. Testfunktionen, die nach gesetzten Bits suchen und
3. Schiebebefehle, die Bitsequenzen verschieben oder rotieren lassen.
11.1
Logische Verknüpfungen
Bitmasken sind Bit-Sequenzen, die zur Modifikation anderer Bit-Sequenzen dienen. Bitmaske A und zu modifizierende Bit-Sequenz B werden “übereinandergelegt” und nach einer der in Tabelle 22 angegebenen Regeln modifiziert.
Assemblerbefehle stehen für die
logischen Verknüpfungen and, or,
A B A and B A or B A xor B
xor und not zur Verfügung. Die Syn0 0
0
0
0
tax hat folgende Form für die logi1 0
0
1
1
sche Verknüpfung Opcode und die
0 1
0
1
1
Operanden bitmaske A und bitse1 1
1
1
0
quenz B. Der Befehl lautet am Beispiel des logischen UND, wenn die
Tabelle 22: Logische Operationen
Bitmaske in al und die zu modifizierende Bitsequenz in cl stehen.
OpCode
andb
bitmaske A, bitsequenz B
%al,%cl
bitmaske A und bitsequenz B können Register und Speicherwerte als Bytes,
Wörter oder Doppelwörter sein, A auch ein Festwert.
Wichtige Anwendung für die logischen Operationen sind Ausschalten von
Bits (and), Einschalten von Bits (or) und Komplementieren von Bits (xor).
Beispiele:
1. Die linke Hälfte des Registers al soll komplementiert werden, der Rest
soll unverändert bleiben:
xorb
0xf0,%al
# (0xf0 = 1111 0000b)
2. In Register %esi soll das low word gelöscht werden:
andl
0xff00,%esi
3. In Register bp sollen die Bits 1, 3 und 6 eingeschaltet werden:
orw
0x4a,%bp
Weitere zur Bitmanipulation dienliche Operatoren sind:
not: Bildung des 1er-Komplementes
neg: Bildung des 2er-Komplementes
60
not A
1
0
1
0
Befehl
bsf op1,op2
bsr op1,op2
bswap op
bt op1,op2
Beschreibung
Bit Scan Forward
Sucht in op1 nach dem ersten gesetzten Bit, beginnend bei Bit Nr.0, die Nr. des Bit wird in op2 gespeichert und zf wird gelöscht. Wird kein gesetztes Bit
gefunden, wird zf gesetzt.
Bit Scan Reverse
Sucht in op1 nach dem ersten gesetzten Bit, beginnend bei Bit Nr.15 bzw. Nr.31, die Nr. des Bit wird
in op2 gespeichert und zf wird gelöscht. Wird kein
gesetztes Bit gefunden, wird zf gesetzt.
Byte Swap
Die Reihenfolge der Bytes wird vertauscht
Bit Test
Prüft in op2, ob das Bit an der in op1 gegebenen
Position gesetzt ist. Falls ja, wird cf auf 1 gesetzt.
op1
r16,m16
r32,m32
r16,r32
i8,i8
Bit Test and Complement
Wie bt, zusätzlich wird das geprüfte Bit invertiert.
r16,r32
i8,i8
btr op1,op2
Bit Test and Reset
Wie bt, zusätzlich wird das geprüfte Bit auf 0 gesetzt.
r16,r32
i8,i8
bts op1,op2
Bit Test and Set
Wie bt, zusätzlich wird das geprüfte Bit auf 1 gesetzt.
r16,r32
i8,i8
Tabelle 23: Befehle zum Test von Wörtern und Doppelwörtern auf einzelne Bits
mit kurzer Erläuterung der Funktionalität und der Bedingungen
Testfunktionen
Für den Test von Bitsequenzen auf gesetzte Bits stehen der Prozessorbefehl
test, der einen logischen Vergleich mit einer Bitmaske durchführt, und eine
Reihe von Prozessorbefehlen zur Verfügung, die in einer Bitsequenz nach gesetzten Bits suchen.
test
op1,op2
Der Operand op1 stellt die Maske dar, op2 ist der zu prüfende Operand.
Bit 1 in der Maske gibt an, dass das entsprechende Bit in op2 getestet werden
soll. test wirkt wie and, ohne jedoch die Bit-Sequenz op2 zu ändern, d. h. ohne
das Ergebnis zu speichern. Das Ergebnis kann über die Zero-flag zf abgefragt
werden (jz oder jnz). zf wird gesetzt, wenn kein getestetes Bit gesetzt (=1) war
und wird gelöscht, wenn mindestens ein getestetes Bit gesetzt war. Beachten
Sie die Parallele zu den Befehlen cmp bzw. sub.
Weitere Testbefehle sind in der Tabelle 23 zusammengefasst.
61
r16
r32
r32
btc op1,op2
11.2
op2
r16,m16
r32,m32
r16,m16
r32,m32
r16,m16
r32,m32
r16,m16
r32,m32
r16,m16
r32,m32
r16,m16
r32,m32
r16,m16
r32,m32
r16,m16
r32,m32
Opcode
shl/shr
Aktion
Bit-Sequenz nach links/
rechts verschieben
Bemerkung
Auffüllen mit 0, Ausgesondertes Bit → cf, Vorzeichenlose Zahlen
sal/sar
Bit-Sequenz nach links/
rechts verschieben
MSB wird restauriert, Ausgesondertes Bit
→cf, Vorzeichen-Zahlen, sal≡shl
rol/ror
Bit-Sequenz nach links/
rechts rotieren
Auffüllen mit ausgesondertem Bit, cf wie oben
rcl/rcr
Bit-Sequenz nach links/
rechts rotieren
Bit-Sequenz & cf bilden 9- bzw. 17-Bit-Einheit
Tabelle 24: Schiebe- und Rotationsbefehle
11.3
11.3.1
Schiebeoperationen
Überblick
Die mögliche Arten von Befehlen zum Schieben und Rotieren von Bits Bytes,
Wörtern und Doppelwörten sind in Tabelle 24 aufgezählt. Für die Anwendung
der Befehle gilt:
Form:
Opcode AnzahlSchiebungen, BitSequenz
Dabei:
Bitsequenz:
Register und Speichergrößen, Bytes, Wörter und Doppelwörter
AnzahlSchiebungen: 1, Register cl, 8 Bit-Festwert
11.3.2
Shift Left- und Shift Right-Operationen
Beim einfachen Schieben von Bit-Sequenzen werden freiwerdende Stellen mit
Nullen aufgefüllt, das letzte herausgeschobenen Bit wird in dem Carry flag
gespeichert. Beispiele:
movb
shlb
$0b11001100,%dl # dx=0xcc=204
$3,%dl
# Ergebnis ist dl=01100000b,CF=0
movb
shrb
$0b11001100,%dl
$3,%dl
# Ergebnis ist dl=00011001b,CF=1
movw
shlw
$0xcc,%dx
$3,%dx
# dx=204
# dx=0x660=1632=23*204
movw
shrw
$0xcc,%dx
$3,%dx
# dx=204
# dx=0x19=25=204/23 (Rest 4)
andb
shrb
$0b11000000,%al
$3,%al
shlb
shrb
$2,%al
$5,%al
oder
62
Wichtige Anwendung sind die schnelle Multiplikation und Division von ganzen Zahlen mit bzw. durch ganze Zahlen. Jeder Schiebeschritt (shl bzw. shr)
bedeutet Multiplikation mit 2 bzw. Division durch 2. Die Multiplikation einer
in %edx stehenden Zahl mit
movl
mull
$2,%eax
%edx
benötigt im Pentium-Prozessor 10 und im 80486-Prozessor 40 Takte. Sie kann
mit
shll
$1,%edx
in 1 Takt (Pentium) bzw. 2 Takten (80486) ausgeführt werden. Die Multiplikation mit 8 braucht bei Verwendung mehrerer Schiebeoperationen mit
shll
$3,%edx
1 Takt bzw. 2 Takte.
Eine weitere Anwendung ist die Isolation einzelner Bits.
11.3.3
Shift Arithmetic Left/Right (sal, sar)
Die Befehle sal und sar dienen der Verarbeitung von vorzeichenbehafteten Zahlen. Der Unterschied zu den gewöhnlichen Shift-Befehlen besteht darin, dass
beim Verschieben nach rechts das Vorzeichenbit nach jeder Verschiebung um
ein Bit wiederhergestellt wird. Der Links-Schiebebefehl sal ist identisch zu shl.
11.3.4
Rotate-Befehle (rol, ror, rcl, rcr)
Die Befehle rol, ror arbeiten wie die Befehle shl, shr (auch hinsichtlich des
carry-flag) mit dem Unterschied, dass die freiwerdenden Bitstellen nicht mit
Nullen, sondern mit den auf der anderen Seite herausgeschobenen Bits aufgefüllt werden. Die Rotate-Befehle können benutzt werden, um Teilbereiche
innerhalb eines Bytes oder eines Wortes auszutauschen, z. B. um die rechte
und linke Bithälfte zu vertauschen.
Beispiele:
movl
rolb
rolw
$0b10110010,%al
$4,%al
# al = 00101011 und cf=1
$8,%ax
# al und ah vertauscht
Bei den Befehlen rcl, rcr (rotate with carry) bilden die Bitsequenz (Byte,
Wort oder Doppelwort) mit dem carry-flag eine 9-Bit-, 17-Bit- oder 33-BitEinheit, wobei das carry flag Bit Nr. 8, 16 bzw. 32 darstellt. Beim Rotieren
werden die Bits durch das carry-flag hindurchgeschoben.
63
12
Stringverarbeitung
Strings (Zeichenketten) sind zusammenhängende Folgen (Tabellen) von Bytes,
Wörtern oder Doppelwörtern. Strings können als Zeichenketten mit den bisher
bekannt gewordenen Prozessorbefehlen verarbeitet werden. Zum einfacheren
und schnelleren Umgang mit Strings verfügt der Intelprozessor über spezielle
Befehle. Diese Befehle zur Stringverarbeitung erzeugen einen effizienteren Code für die Programmierung von Stringmanipulationen. Sie lassen sich in zwei
Gruppen aufteilen:
• Einfache Stringbefehle ohne Wiederholung
• Wiederholungsbefehle
Ein einfacher Stringbefehl kann durch ein sogenanntes Präfix zu einem
Wiederholungsbefehl umgewandelt werden.
• Übersetzungsbefehl xlat.
Eine gewisse Sonderposition nimmt die Funktion xlat ein, die eine Übersetzung von Bytes mit Hilfe einer Tabelle gestattet.
12.1
Einfache Stringbefehle
Die einfachen Stringbefehle sind in Tabelle 25 zusammengestellt. Für sie gilt:
• Die genannten Stringbefehle werden ohne Operanden benutzt.
• Alle einfachen Befehle zur Stringverarbeitung beziehen sich nur auf ein
Stringelement (Byte, Wort oder Doppelwort). Der gewünschte Datenblock wird durch das OpCode-Suffix ausgewählt.
• Die Adressen der referenzierten Bytes, Wörter oder Doppelwörter der
Strings werden in den Registern esi (Quellstring) und edi (Zielstring)
erwartet.
• Nach der Operation werden esi und edi inkrementiert, wenn das directionflag df = 0 ist, und dekrementiert, wenn das direction-flag df = 1 ist. Das
direction-flag wird mit std gesetzt und mit cld gelöscht.
12.2
Wiederholungsbefehle
Neben der bekannten Schleifenbildung (siehe Abschnitt 8.3) gibt es eine besonders einfache Variante durch das Wiederholungspräfix rep. Der Wiederholungsbefehl rep kann nur bei Wiederholung einer einzelnen Befehlszeile benutzt
werden und verwendet das Register ecx als Wiederholungszähler. Er bildet eine sogenannte Hardwareschleife. Dies bedeutet, dass der in der Schleife auszuführende Befehl nur einmal, und nicht bei jedem Schleifendurchlauf, von der
Fetch-Unit des Prozessors in die execute Unit geladen werden muss. Die Befehlssequenz
rep
movsb
64
Funktion
movsb
movsw
movsl
cmpsb
cmpsw
cmpsl
scasb
scasw
scasl
lodsb
lodsw
lodsl
stosb
stosw
stosl
Aktion
Kopiere ein Byte von Quellstring (Index esi) nach Zielstring (Index edi)
Kopiere ein Wort von Quellstring (Index esi) nach Zielstring (Index edi)
Kopiere ein Doppelwort von Quellstring (Index esi) nach Zielstring (Index edi)
Vergleiche die Bytes von Quellstring (Index esi) und Zielstring (Index
edi)
Vergleiche die Wörter von Quellstring (Index esi) und Zielstring (Index
edi)
Vergleiche die Doppelwörter von Quellstring (Index esi) und Zielstring
(Index edi)
Suche nach dem Zeichen al in Zielstring (Index edi)
Suche nach dem Wort ax in Zielstring (Index edi)
Suche nach dem Doppelwort eax in Zielstring (Index edi)
Lade das Byte aus Quellstring (Index esi) nach al
Lade das Wort aus Quellstring (Index esi) nach ax
Lade das Doppelwort aus Quellstring (Index edi) nach eax
Speichere das Byte in al nach Zielstring (Index edi)
Speichere das Wort in ax nach Zielstring (Index edi)
Speichere das Doppelwort in eax nach Zielstring (Index edi)
Tabelle 25: Liste der einfachen Stringbefehle. Die Adressen der betrachteten
Bytes, Wörter oder Doppelwörter müssen zuvor in die Register esi bzw. edi
gespeichert werden.
ist durch einen Ablaufplan in Abbildung 7 beschrieben. Die Schleife muss in 2 Zeilen geschrieben werden.
1. Der Inhalt von Register ecx wird auf 0 überprüft. Wenn ecx 6= 0, wird der nachfolgende
Befehl ausgeführt, nämlich
ecx dekrementieren
esi und edi inkrementieren
(df=0) oder
dekrementieren (df=1)
(a) ein Byte wird im Arbeitssspeicher von
Adresse esi nach Adresse edi kopiert,
Byte (esi) nach Byte (edi)
kopieren
(b) esi und edi werden inkrementiert (df =
0) bzw. dekrementiert (df =1 ),
ecx = 0 ?
(c) ecx wird dekrementiert,
2. die Schleife wird verlassen, wenn der Inhalt
von ecx Null ist.
Im folgenden Beispiel werden 35 Byte einer
Zeichenkette, die ab der Adresse SOURCE abgelegt ist, an eine andere Stelle des Arbeitsspeichers
ab der Adresse DESTINATION kopiert:
cld
Abbildung 7: Ablaufplan des Wiederholungsbefehls rep mit movsb als Schleifenkörper
# Vorwärtskopieren
65
leal
leal
movl
rep
movsb
SOURCE,%esi
# Anfangsadresse des Quellstrings
DESTINATION,%edi # Anfangsadresse des Zielstrings
$35,%ecx
# 35 Bytes sollen kopiert werden
Einige Wiederholungsbefehle (repe, repne, repz, repnz) enthalten eine zweite Abbruchbedingung, die für die Vergleichsoperationen cmps und scas verwendet werden kann.
repe,repz Wiederholung, solange ecx 6= 0 und bei Gleichheit im Vergleich
repne,renpz Wiederholung, solange ecx 6= 0 und bei Ungleichheit im Vergleich
Weitere Anwendungen für Stringbefehle mit Wiederholung sind Vergleich
von Texten (cmps), Suche nach bestimmten Zeichen (scas), Ersetzen von bestimmten Zeichen (lods, stos) und Verschieben von Feldern im Arbeitsspeicher
(movs). Die Harwareschleife rep wird sinnvoll nur mit den Befehlen movs und
stos eingesetzt, die erweiterten Hardwareschleifen repe/repz und repne/repnz
nur mit den Befehlen sca und cmps (Warum wohl?).
Aufgabe: Wie kann ein Text von 250 Zeichen um 15 Byte in Vorwärtsrichtung verschoben werden, ohne Teile des Textes vor dem Kopieren zu zerstören?
12.3
Übersetzungsbefehl xlat
Die Wirkungsweise von xlat sei am Beispiel der hexadezimalen Darstellung
einer Zahl zwischen 0 und 16 erläutert. Die Zahl steht (binär verschlüsselt) an
der Adresse ZHL. Zunächst wird eine Übersetzungstabelle definiert, im Beispiel:
HEXTAB:
.ascii
’’0123456789abcdef’’
Diese Übersetzungstabelle besteht aus 16 Zeichen und ordnet jeder Zahl
(Index) von 0 bis 15 ein Zeichen zu, z. B. der Zahl 11 das Zeichen b oder der
Zahl 4 das Zeichen 4. Die Zahl (Index) muss jeweils im Register al stehen, die
Anfangsadresse der Übersetzungstabelle wird im Register ebx erwartet. Wenn
die Register al und ebx vorbelegt sind, kann der Befehl xlat gegeben werden.
leal
HEXTAB,%ebx
movb
ZHL,%al
xlat
/* Ausgabe des Zeichens */
. . .
Ist der Inhalt von al 0b00001101, so wird durch xlat das 14te Zeichen der
Übersetzungstabelle, also d nach al kopiert.
Zur Ausgabe größerer Zahlen in hexadezimaler Form müssen diese in Quadrupel von Bits (nibble) aufgelöst werden. Dazu eignen sich die Befehle aus
Abschnitt 11 (Bitmasken, Schiebeoperationen). Die nibbles werden in der gezeigten Weise in Zeichen umgesetzt. Die hexadezimale Ausgabe von Zahlen
ist deswegen besonders einfach und schnell, da keine Rechenoperationen nötig
sind.
Wichtige Anwendungen sind neben der Ausgabe von (binär) im Rechner gespeicherten Zahlen in hexadezimaler Form die Modifizierung von Zeichensätzen.
66
13
Hochsprachenprogramme
Assemblerbefehle oder Assemblermodule werden in Programmen verwendet,
um für häufig durchlaufene, zeitkritische Teile einen optimalen Maschinencode
zu erzwingen, um eine bequeme Schnittstellenprogrammierung zu ermöglichen
oder um Prozessorbefehle auszunutzen, die von den Compilern nicht unterstützt
werden. Letzteres gewinnt durch zunehmende Bedeutung von Multimedia-Anwendungen
zunehmend an Bedeutung und führte zur Erweiterung der Befehlssätze von
Prozessoren. Bei Intel-Prozessoren läuft dies unter der allgemeinen Bezeichnung SIMD (Single Instruction - Multiple Data) mit den Ausführungen MMX
(MultiMedia eXtension) und SSE2 (Streaming SIMD Extension 2), bei AMDProzessoren unter 3DNow!.
Assemblerbefehle können in zweierlei Weise in Hochsprachenprogrammen
benutzt werden.
1. Assemblerbefehle können direkt in den Hochsprachen-Programmcode eingetragen werden. Dort werden sie von einem Inline-Assembler verarbeitet, der in den Hochsprachen-Compiler integriert ist und die wichtigsten
Assemblerbefehle kennt.
2. Assemblerbefehle können in genuinen Assembler-Prozeduren (siehe Abschnitt 9) zu Modulen zusammengefasst für bestimmte Teilaufgaben eingesetzt werden. Die Prozeduren werden zu Objektmodulen assembliert
und durch den Linker zu anderen z. B. aus Hochsprachen-Quelldateien
stammenden Objekt-Modulen gebunden.
Die Bedingungen, unter denen der Einbau von Assemblerbefehlen vorgenommen wird, ist stark von der verwendeten Hochsprachen und dem benutzten
Compiler abhängig. Für die Betrachtungen in diesem Manuskript ist der GNU
C-Compiler gcc (damit ist im Folgenden der C++ Compiler g++ eingeschlossen) zugrunde gelegt.
13.1
Inline-Assemblerbefehle
Die Verwendung von Assemblerteilen in Hochsprachenprogrammen in Form
von Assemblerprozeduren ist wegen des Overheads bei Prozeduraufruf und
Prozedurbeendigung nur sinnvoll, wenn größere Programmteile in Assembler
formuliert werden. Für kleinere Programmteile können Assemblerbefehle direkt in den Hochsprachen-Code integriert werden. Eine Assemblerzeile hat die
folgende Form:
asm (feld1 : feld2 : feld3 : feld4 )
Die Felder werden durch Doppelpunkte getrennt. Besondere Beachtung verdienen die Schnittstellen zwischen dem Hochsprachenteilen und dem Assemblerteil. Die Schnittstellen regeln die Übergabe von Größen, die im Hochsprachenteil deklariert wurden, in den Assemblerteil (Eingabe) und umgekehrt (Ausgabe). Dort können die Hochsprachen-Variablen bestimmten Registern zugeordnet werden.
Im Fall der Hochsprache C/C++ ergibt sich folgende Form der Einbindung
von Assemblerbefehlen in ein C-Programm:
67
feld1
feld2
feld3
feld4
Im ersten Feld, dem Befehlsfeld, steht in Hochkomma der Assemblerbefehl, wobei Register durch doppelte %-Zeichen eingeleitet werden. Als
Platzhalter für die Hochsprachenvariable wird %0 verwendet.
Das zweite Feld, das Ausgabefeld, enthält einen Ausgabeparameter
(Ausgabe aus Sicht des Assemblerteils). Der Datentyp wird mit d (ganze
Zahlen) oder f (Gleitkommazahlen) gekennzeichnet. Das Zeichen = steht
bei der Ausgabe. In der Klammer steht der Variablenname.
Das dritte Feld ist das Eingabefeld. Es enthält analog zu feld2 einen
Eingabeparameter.
Im das vierte Feld kann das veränderte Register geschrieben werden
(mit nur einem %-Zeichen).
int main ()
{
. . . . .
// C-programmzeilen
asm("movl %0, %%edx":/* keine Ausgabe */:"d "(C_VAR1):"%edx");
//Übergabe der C-Variablen C_VAR1 ins Register edx
asm("movl %0, %%ecx":/* keine Ausgabe */:"d "(C_VAR2):"%ecx");
//Übergabe der C-Variablen C_VAR2 ins Register ecdx
asm(" Assemblerbefehle ");
asm(" Assemblerbefehle ");
. . . . .
// Weitere Assemblerbefehle
asm("movl %%eax,%0":/*"=d "(C_VAR1):/* keine Eingabe */:/* keine Änderungen */);
//Übergabe des Registers in die C-Variablen C_VAR1
. . . . .
}
// C-programmzeilen
Im folgenden Beispielprogramm zur Berechnung der Fakultät wird eine vereinfachte Schreibweise benutzt.
/* Beispielprogramm für Einbettung von Assemblerzeilen */
/* in ein C-Programm zur Berechnung der Fakultät */
#include<iostream.h>
int main ()
{
int n, su;
cout << "Anzahl? "; cin >> n;
/* Übergabe der Variablen n in Register ecx */
asm("movl %0, %%ecx::"d "(n):"%ecx");
asm("movl $1, %eax");
asm("movl $0, %esi");
asm("LB:");
68
asm("incl %esi");
asm("mull %esi");
asm("cmpl %ecx, %esi");
asm("jne LB");
/* Übergabe des Registers eax in die Variable su */
asm("movl %%eax, %0":"=d "(su));
cout << "Fakultät: n! = " << su << endl;
}
Dieses Beispiel soll noch in einer anderen Syntax dargestellt werden:
/* Beispielprogramm für Einbettung von Assemblerzeilen */
/* in ein C-Programm zur Berechnung der Fakultät */
#include<iostream.h>
int main ()
{
int n, su;
cout << "Anzahl? "; cin >> n;
asm(
"movl $1, %eax;"
"movl $0, %esi;"
"LB:;"
"incl %esi;"
"mull %esi;"
"cmpl %ecx, %esi;"
"jne LB;"
/* Übergabe des Registers eax in die Variable su */
/* Übergabe der Variablen n in Register ecx */
:"=a (su):"=c "(n));
cout << "Fakultät: n! = " << su << endl;
}
Diese Schreibweise enthält in feld1 sämtliche Assemblerzeilen (durch Semikolon
abgeschlossen und in Gänsefüßchen eingeschlossen) dahinter das Ausgabefeld
und das Eingabefeld. Das optionale feld4 ist weggelassen. Durch die Buchstaben
in den Ein-/Ausgabefeldern wird das betroffene Register festgelegt. Folgende
Zuordnung gilt:
Buchstabe
a
b
c
d
S
D
Register
eax
ebx
ecx
edx
esi
edi
In einem weiteren Beispiel werden mehrere C-Variable in die Register des Assemblerteils übergeben.
69
/* Beispielprogramm für Einbettung von Assemblerzeilen */
/* in ein C-Programm */
/* Einbringen von mehreren C-Variablen */
#include<iostream.h>
int main()
{
int n1=3, n2=5, n3=7;
int su;
/* Übernahme der Variablen n1, n2, n3 in die Register eax, ebx, ecx
asm("movl %0,%%eax"::"d "(n1):"%eax");
asm("movl %0,%%ebx"::"d "(n2):"%ebx");
asm("movl %0,%\ecx"::"d "(n3):"%ecx");
asm("addl %ecx, %ebx");
asm("addl %ebx, %eax");
/* Übergabe des Registers eax in die Variable su */
asm("movl %%eax,%0":"=d "(su));
cout << "Summe (3+5+7) = " << su << endl;
}
13.2
Verbindung von Assembler- und Hochsprachenmodulen
Aus Sicht des Prozessors ist das zu verarbeitende Modul (Prozedur, Programm)
unabhängig von der Art der Entstehung. Die Art der Entstehung (Assemblierung, Compilierung) hängt von der Art der zu erstellenden Software und den
Vorstellungen des Programmierers ab. Grundsätzlich entstehen im ersten Bearbeitungsschritt sogenannte Objektdateien mit Objektcode, die durch einen weiteren Schritt (“Linker”) miteinander verbunden werden müssen, um in gewünschter Weise zusammenarbeiten zu können (siehe Abschnitt 4.3). Im allgemeinen
erfolgt die Erstellung der Objektdateien (Prozeduren) unabhängig voneinander.
Sollen auf verschiedene Weise erstellte Objektdateien zu einer ausführbaren Datei zusammengefasst werden, ist wegen unterschiedlicher Konventionen
(Aufruf, Speichermodell, Parameterübergabe, Datentypen, ... ) eine Vorbereitung der Hochsprachenprozeduren ebenso wie der Assemblerprozeduren nötig.
Im Falle des GNU C-Compilers, in dem die Assemblierung in erster Linie als
Zwischenstufe zur Objektdatei zu verstehen ist und weniger als eigenständige
Methode, Objektcode zu erzeugen, liegen besondere Verhältnisse vor, die weiter
unten beschrieben werden.
Der Compiler regelt gegebenenfalls durch Optionen gesteuert, in welche
Segmentstruktur das Maschinenprogramm eingebettet werden soll. So ordnet
z. B. Turbo Pascal unter DOS jeder aufgenommener Unit ein eigenes Codesegment zu und gestattet nur ein Datensegment. Das von der Assemblerprozedur
gewählte Speichermodell muss diese Speicherstruktur unterstützen. Die Aufrufkonventionen regeln die Art der Parameterablage auf dem Stack und die
Stackbereinigung beim Rücksprung in die rufende Prozedur. Beim Aufruf einer Assemblerprozedur aus einem Pascal- Programm werden die Parameter in
der Reihenfolge des Auftretens in der Parameterliste auf den Stack gebracht,
beim C-Compiler in umgekehrter Reihenfolge. Danach wird die Rücksprung-
70
Parameter- bzw. Ergebnistyp
C
Turbo Pascal
char,
char, boolean, byte,
shortint
int,short,short
integer,word
int,signed,unsigned
long,long int
longint,comp
Adresse
Adresse
Länge
Parameter
byte
1 Wort
Ergebnisregister
al
word
1 Wort
ax
long
long
2 Wörter
2 Wörter
eax
eax
Tabelle 26: Übergabe des Funktionswertes bei Hochsprachen-Funktionen
adresse gesichert. Die Stackbereinigung in einer Assemblerprozedur wird bei
Aufruf aus C-Modulen vom rufenden Programm durchgeführt, bei Pascal muss
die Stackbereinigung in jedem Modul vor dem Rücksprung vorgenommen werden.
Im Gegensatz zu manchen anderen Hochsprachen kennt C nur Funktionen. Der Funktionswert wird von der rufenden Prozedur im Register eax erwartet. Wird also im C-Modul ein Funktionswert angesprochen, so muss in
der Assemblerprozedur das Register eax ( bzw. al, ax, dx:ax, edx:eax je nach
Funktionstyp) belegt werden. Die Übergabe eines Funktionswertes wird bei
Pascal ebenso durch das Register eax vorgenommen. Die Parameterübergabe
erfolgt - wie bei anderen Hochsprachen - durch Wertübergabe (“call by value”)
oder durch Adressübergabe (“call by reference”). Auch Nicht-Basisdatentypen
können - weniger empfehlenswert - durch call by value übergeben werden. Sie
werden dabei im Stackframe der rufenden Prozedur abgelegt.
13.3
GNU-Konventionen
Der GNU C-Compiler erstellt aus einer C-Quelldatei in mehreren Zwischenstufen die ausführbare Datei. In einer ersten Zwischenstufe wird der C-Code
in einen Assembler-Code umgewandelt. Mit der Option -S kann z. B. der Umwandlungsvorgang bei der Stufe des Assembler-Code angehalten werden. Dabei
erzeugt der C-Compiler lediglich Assembler-Code.
Die Verbindung von unterschiedlichen Modulen ist deswegen ziemlich einfach, da der GNU C-Compiler in der Lage ist, unterschiedliche Module anhand der Erweiterung des Dateinamens zu erkennen und an die anderen Module anzubinden. Dabei sind C-Quelldateien mit Erweiterung .c, AssemblerQuelldateien mit der Erweiterung .s und Objektdateien mit der Erweiterung
.o zu versehen. Soll ein Assembler-Modul als Programm (siehe Abschnitt 9.1)
verwendet werden, muss lediglich die Standard-Startadresse start: durch die
für C-Programme übliche Startadresse main ersetzt werden.
Beispiel: Ein C-Programm prgtest.c benutzt eine Assemblerprozedur calcul.s und eine in eigener Datei vorliegende C-Funktion errhndl.c. Die Module
sind so gestaltet, dass sie aufeinander abgestimmt sind (siehe 13.2). Dann erzeugt folgende Kommandozeile eine ausführbare Datei prgtest.x:
gcc -o prgtest.x prgtest.c calcul.s errhndl.c
71
14
14.1
Programmierung des Koprozessors
Ganze Zahlen
Im Koprozessor existieren die in Tabelle 27 aufgeführten Formate für ganze Zahlen. Sie werden in ähnlicher Weise wie beim Hauptprozessor definiert
(Direktiven) und verwendet (OpSize-Suffices). Das 64-Bit-Ganzzahlenformat
existiert nur in der internen Darstellung des Koprozessors.
Direktiven
Binärzahlen
.word
.long, .int
.quad
gepackte BCD-Zahlen
(2 Dezimalziffern pro Byte)
Anzahl
der Bits
16
32
64
80
OpSizeSuffix
s
l
q
Tabelle 27: Datenformate des Koprozessore für ganze Zahlen
14.2
14.2.1
Gleitkommazahlen
Mantisse und Exponent
Bei sehr großen und sehr kleinen Zahlen ist eine Darstellung als ganze Zahlen insbesondere in dualer Form nicht praktizierbar. Solche Zahlen werden als
Gleitkommazahlen, also durch Angabe von Mantisse und Exponent angegeben.
Die Entfernung zwischen Mond und Erde ist ungefähr rEM = 384.400.000m =
0b10110111010010111101010000000m, als Gleitkommazahl 3, 844 · 10 8 . In den
folgenden Darstellungen sind die ersten beiden Zeilen in dezimaler, die anderen
in binär/hexadezimaler Schreibweise:
Schreibweise
3, 844 · 108
0, 3844·9
1, 0110111010010111101010000000 · 211100
101101110, 10010111101010000000 · 210100
14.2.2
Mantisse
3, 844
0, 3844
1, 6e97a80
16e, 97a80
Exponent
8
9
0 × 1c
0 × 14
Lage des Dezimalpunktes
Die Lage des “Dezimalpunktes” wird in der Regel so gewählt, dass die Mantisse
einen Wert zwischen 1 (incl.) und dem Basis-Wert annimmt. Bei dualer Darstellung wird der Dezimalpunkt so gesetzt, dass die Ziffer vor dem Dezimalpunkt
eine 1 ist.
14.2.3
Darstellung im Koprozessor 80 ×87
Zur Definition einer Gleitkommazahl im Rechner (Prozessor, Arbeitsspeicher)
werden Vorzeichen, Mantisse und Exponent benötigt. Der Koprozessor 80 ×87
verwendet die duale Darstellung mit der 1 vor dem Komma. Mit dieser impliziten Festlegung kann ein Bit gespart und damit die Genauigkeit der Darstellung
erhöht werden. Gespeichert werden also
72
Direktive
OpSize-Suffix
Anzahl der Bits
Vorzeichen
Exponent
Mantisse
Vorkommateil
Exponentenbias
Genauigkeit (Dezimalst.)
Bereich (Zehnerpot.)
Short Real
Long Real
.float,
.single
s
32
1
8
23
0
128
7
±38
.double
10-Byte
Real
.tfloat
l
64
1
11
52
0
1024
16
±308
t
80
1
15
63
1
16384
19
±4932
Tabelle 28: Typen von Gleitkommazahlen im Koprozessor 80 ×86 (IEEEFließkommaformate). Die 10-Byte-Real-Gleitkommazahl wird nur intern benutzt.
V o rz e ic h e n b it
E x p o n e n t
6 3 6 2
M a n tis s e
5 2 5 1
3 2 3 1
0
Abbildung 8: Darstellung der Long Real Gleitkommazahl
• das Vorzeichen,
• die Mantisse ohne die 1 vor dem Komma und
• der Exponent
Der Exponent wird in der Exzessdarstellung gespeichert, also mit einem Bias versehen, um negative Exponenten darzustellen (normalisierter Exponent).
Dies ist in Tabelle 28 und Abbildung 8 veranschaulicht.
Beispiel: Der genaue Wert für den Abstand Erde / Mond beträgt rEM
= 384.428.150m = (0b)10110111010011000100001110110m
= (0b)1, 0110111010011000100001110110 · 2 11100 m.
Diese Zahl soll als Short Real Gleitkommazahl im IEEE-Fließkommaformat
geschrieben werden. Die Mantisse wird implizit mit einer “1” als Vorkommazahl
geschrieben, der Dezimalteil besteht aus 23 Bit. Zum Exponenten 1 1100 wird
der Bias 1000 0000 (=0×80) addiert. Bit 31 enthält das Vorzeichen (0 für +).
Damit besteht die Zahl in der Internen Darstellung aus folgenden Bits: 0100
1101 1011 0111 0100 1111 0100 0011.
Bei Vergleich der Mantissen werden Sie feststellen, dass nicht alle Bits der
genannten Zahl in der REAL4-Darstellung enthalten sind.
Wie groß ist die Abweichung? Schreiben Sie die im Beispiel benutzte Zahl als
Short Real- und als Long Real-Gleitkommazahl in hexadezimaler Darstellung!
73
7 9
S T
S T
S T
S T
S T
S T
S T
S T
(1
(2
(3
(4
(5
(6
(7
)
)
)
)
)
6 4 6 3
0
)
en n t
p o
E x
)
sse
i
t
n
M a
V o rz e ic h e n b it
Abbildung 9: Datenregister des Koprozessors 80 ×87
14.3
Die Architektur des 80 ×87-Koprozessors
Der Koprozessor 80 ×87 ist ein eigenständiger Prozessor mit
• Zugriff auf den Arbeitsspeicher
• eigenen Daten- und Steuerungsregistern
• eigenem Befehlssatz für den Datentransfer
aber ohne direkten Datenaustausch mit der CPU (Prozessor).
Die in Abbildung 9 dargestellten Datenregister haben das tfloat-Format (80
Bit). Da extern nur die 32- bzw. 64-Bit-Formate benutzt werden, führen die Datentransferbefehle eine Formatkonvertierung durch. Die Register sind als Stack
organisiert, d. h. es gibt Befehle mit push- und pop-Funktionen. Mit dem Befehl fld memvar (fld bedeutet floating point number load) wird beispielsweise
die Speichervariable memvar aus dem Arbeitsspeicher in das Datenregister st
(=top of stack) geladen. Der vorige Stackinhalt wird nach unten geschoben.
Der Koprozessor verfügt über 7 weitere 16-Bit-Register, die als Steuerregister bzw. Umgebungsvariable dienen. Diese Register enthalten Informationen
zur Steuerung, zum Status sowie den aktuellen Stand der Befehls- und Operandenzeiger zur Bearbeitung von Ausnahmezuständen.
14.4
Befehle, Formate
Die Syntax der Prozessorbefehle des 80 ×87 ist aufgebaut aus dem Präfix f (für
floating point processor), dem eigentlichen Befehlsteil (z. B. add für Addieren
oder ld für Laden) und den Operanden. Operanden können explizit (bis zu 2)
oder implizit definiert werden. Die Tabellen 29 und 30 geben einen Überblick
über die wichtigsten Befehlstypen mit Beispielen.
Aus der großen Zahl der Befehle des Koprozessors sind einige Beispiel herausgegriffen, die für Laden und Speichern, Arithmetik und Programmsteuerung
zur Verfügung stehen.
74
Befehlstyp
Synthax
A
B
C
Stack
Arbeitsspeicher
Register
D
Register Pop
fbefehl
fbefehl memory
fbefehl st(x),st
fbefehl st(x)
fbefehlp st(x),st
implizite explizite
Operanden
st,st(1)
st
memvar
st,st(x)
st,st(x)
st(x),st
Beispiel
fadd
fadd memvar
fadd st(3),st
fadd st,st(3)
faddp
Tabelle 29: Befehlstypen des Prozessors 80 ×87
A
fld1
st
1
st(1)
*
fldpi
3,14..
1
faddp
4,14
*
B
st
st(1)
Lädt 1 nach st, der vorherige Inhalt von st
wird nach st(1) weitergeschoben (soviel wie
push!)
Lädt π nach st, die 1 wird nach st(1) weitergeschoben
st(st+st(1), der vorherige Inhalt von st wird
“weggepopped”
MEM1
MEM2
im Datensegment:
MEM1: .float 1.0
MEM2: .float 2.0
MEM1→st
MEM2→st
dadurch MEM1→st(1)
st←st+MEM1
st→MEM1 und pop
fld MEM1
fld MEM2
1,0
2,0
*
1.0
1,0
1,0
2,0
2,0
fadd MEM1
fstp MEM1
fst MEM2
3,0
1,0
1.0
1,0
*
*
1,0
3,0
3,0
2,0
2,0
1,0
C
st
1.0
1.0
st(1)
2.0
2.0
st(2)
3.0
3.0
4.0
3.0
3.0
Anfangszustand
Addition, Ergebnis im ersten Operand, st(1)
Addition, Ergebnis in st
3.0
4.0
3.0
Vertauschung st(1)/st
st
1.0
2.0
st(1)
2.0
4.0
st(2)
3.0
fadd
%st(1),%st
fadd
%st,%st(2)
fxch %st(1)
D
faddp
%st(2),%st
Anfangszustand
Addition und POP
Tabelle 30: Beispiele zu den Befehlstypen aus Tabelle 29
75
1 5
8
c 3
c 2
c 1
c 0
7
sf
0
z f
o f
p f
c f
Abbildung 10: Inhalte von Statuswort (High Byte, oben) und flag-Register (Low
Byte, unten). Die Pfeile deuten die Übertragungen aus dem Statuswort des
Koprozessors in das flag-Register der CPU an.
1. Laden und Speichern
Laden und Speichern von reellen Zahlen, bei der Endung p mit pop
fild, fist, fistp dto. für Integerzahlen
fldz, fld1, fldpi, fldl2e, fldl2t, fldlg2, fldln2
Laden von Festwerten (0, 1, π,log2 e,log2 10,log2 ln2)
fxch
Vertauschen der Inhalte von st und st(1)
fld, fst, fstp
2. Arithmetik
• fadd, fsub, fsubr, fmul, fdiv, fdivr (Endung r bedeutet umgekehrte Reihenfolge der Operanden).
Diese Befehle gibt es außer für reelle Zahlen (z. B. fadd), für Integerzahlen (entsprechend fiadd) und als Befehle mit zusätzlichem
“pop” (entsprechend faddp oder fiaddp).
• fabs, fchs, frndint (Runden zu Integer), fsqrt, fscale
(st←st*2st(1)), fprem (Rest von st/st(1)←st).
Die Befehle werden alle ohne Operanden verwendet und sind implizit
auf das Register st bezogen.
3. Programmsteuerung
Da der Koprozessor nicht über Sprungbefehle verfügt, muss die Programmsteuerung durch die CPU vorgenommen werden. Dabei wird das
Statuswort (über den Arbeitsspeicher) zur CPU übertragen und dort ausgewertet (siehe Abbildung 10):
fstw
mem16
fwait
movw
sahf
mem16,%ax
# Überträgt das Statuswort in den
Arbeitsspeicher, Adresse mem16
# CPU wartet Übertragung ab
# Inhalt von ah (= High Byte des
Statuswortes) wird in die Bits 0 bis 7
des flag-Registers übertragen
76
Aktion
Daten aus dem
Arbeitspeicher
in KoprozessorRegister laden
Timing
CPU agiert vor
Koprozessor
Koordinierung
Koprozessor
wartet bis CPU
den
Speicherzugriff beendet
hat
nicht
nötig
(CPU
kann
andere Arbeiten
durchführen)
Programmierer
muss sicherstellen, dass CPU
erst
zugreift,
wenn der Koprozessor fertig
ist
Bearbeitung der
Daten im Koprozessor
Daten aus den
KoprozessorRegistern in den
Arbeitsspeicher
speichern
Koprozessor
agiert vor CPU
Lösung
Von Assembler
bzw. CPU organisiert
Befehle
fwait
wait,
Tabelle 31: Koordinierung des Zusammenwirkens von Hauptprozessor und Koprozessor
14.5
Koordinierung des Speicherzugriffes von CPU und
Koprozessor
Beim Zusammenwirken der CPU (80 ×86) mit dem Koprozessor sind folgende
Punkte zu beachten:
• Die Prozessoren arbeiten simultan.
• Die Dateneingabe und Datenausgabe für den Koprozessor ist nur über
die CPU möglich.
• Die Register sind nicht gegenseitig zugänglich, Datenaustausch ist nur
über den Arbeitsspeicher möglich.
Der Ablauf bei der Bearbeitung von Daten im Koprozessor, der sich damit
ergibt, werde an Hand des folgenden Beispiels erläutert:
Ein Doppelwort in eax wird über Speichervariable var32 in den Koprozessor
gebracht, dort bearbeitet und in eax zurückgespeichert:
/* CPU vor dem Koprozessor aktiv */
movw
%eax,var32 # CPU→Memory
fildl
var32
# Memory→Koprozessor (st)
/* Koprozessor wartet von selbst, bis die CPU fertig ist */
/* und führt dann den Transfer Arbeitsspeicher/Koprozessor aus */
. . . Bearbeitung im Koprozessor . . .
/* Inhalt von ST wird als Integer-Zahl im Arbeitsspeicher in
var32 gespeichert */
fist
var32
# Koprozessor (st)→Memory
77
/* CPU wird gezwungen, zu warten bis der Koprozessor mit der
Übertragung in den Arbeitspeicher fertig ist */
fwait
/* CPU setzt ihre Tätigkeit fort */
movl
var32,%eax # Memory→CPU
14.6
Erzeugung von Gleitkommazahlen
Zur Erzeugung von Gleitkommazahlen kann man sich die Eigenschaft nutzbar
machen, dass bei Laden von ganzen Zahlen aus dem Arbeitsspeicher in die
Register des Koprozessors eine Umwandlung in die interne 80-Bit-Darstellung
(10-Byte Real) stattfindet. Dabei können Gleitkommazahlen bei der Eingabe
vom Prozessor in ganze Zahlen aufgeteilt und im Koprozessor wieder zusammengesetzt werden.
Einfacher ist es, für die Eingabe von Gleitkommazahlen Prozeduren bzw.
Funktionen in einer Hochsprache (zweckmäßigerweise in C) zu benutzen (siehe
Abschnitt 13.2).
78
15
Input/Output Ports
Neben dem Datenaustausch mit dem Arbeitsspeicher kann der Prozessor Daten an periphere Geräte über Input/Output Ports (I/O Ports) übermitteln
oder von ihnen empfangen. I/O Ports dienen der Kommunikation (Steuerung,
Datenaustausch) mit peripheren Geräten oder Schaltkreisen wie z. B. Drucker,
Soundkarte, Grafikkarte, Tastatur, ISDN-Karte, Monitor etc. Sie können als
Input Ports oder als Output Ports oder als bidirektionale Ports verwendet werden.
15.1
Adressraum der I/O Ports
Die I/O Ports bestehen aus einzeln adressierbaren 8-Bit Speicherplätzen in einem Adressraum, der physikalisch dem Hauptspeicher angehört, logisch aber
vom Arbeitsspeicher getrennt ist und mit speziellen Befehlen angesteuert wird.
Der Adressraum umfasst 64 kByte und erstreckt sich von 0000 bis ffff. Der
Bereich von 00f8 bis 00ff ist reserviert. Mehrere Bytes können gemeinsam als
Wörter oder Doppelwörter adressiert werden. Die zu einem bestimmten peripheren Gerät oder Schaltkreis gehörige I/O Port-Adresse wird von dem peripheren Gerät oder Schaltkreis angefordert. Häufig ist im peripheren Gerät
oder Schaltkreis die I/O Port-Adresse in bestimmten Grenzen einstellbar. Die
I/O Ports stellen so etwas wie Briefkästen und Postfächer im Verkehr zwischen
peripherem Gerät und Prozessor dar.
In der nachfolgenden Tabelle ist die Zuordnung der I/O Ports am Beispiel
eines PC veranschaulicht. Aus ihr Tabelle wird verständlich, dass für den Zugriff
auf I/O Ports eine Adminstratorberechtigung erforderlich ist.
0000h
0020h
0040h
0060h
0061h
0064h
0070h
0080h
0094h
00A0h
00C0h
00F0h
0170h
0170h
0lF0h
0lF0h
0200h
0220h
0240h
0274h
02F8h
0330h
0376h
-
000Fh
0021h
0043h
0060h
0061h
0064h
0071h
0090h
009Fh
00A1h
00DEh
00F0h
0177h
0177h
01F7h
0lF7h
0207h
022Fh
025Fh
0277h
02FFh
0331h
0276h
DMA - Controller
Programmierbarer Interrupt Controller (PIC)
Systemzeitgeber
Standard (101/102 Tasten) oder Microsoft Natural Keyboard
Systemlautsprecher
Standard (101/102 Tasten) oder Microsoft Natural Keyboard
CMOS-/Echtzeitsystemuhr
DMA-Controller
DMA-Controller
Programmierbarer Controller
DMA-Controller
Numerischer Coprozessor
Zweiter IDE Controller (Dual FIFO)
Intel 82371SB PCI-Bus Master IDE Controller
Erster IDE Controller (Dual FIFO)
Intel 82371SB PCI-Bus Master IDE Controller
Gameport-Joystick
Creative Sound Blaster 16 Plug and Play
AVM ISDN - Controller A1
E/A-Read Data-Anschluss für ISA Plug&Play Enumerator
COM-Anschluss (COM2)
Creative Sound Blaster 16 Plug and Play
Zweiter IDE Controller (Dual FIFO)
79
0276h
0J7Sh
0388h
0JB0h
0300h
0JF2h
0JFGh
0JF6h
03F8h
04D0h
0778h
0CF8h
D800h
E000h
ES00h
E800h
E808h
ES08h
-
0376h
037Fh
03SBh
03BBh
0SDFh
03F5h
03F6h
03F6h
03FFh
04Dlh
077Fh
0CFFh
DSlFh
E0FFh
ES07h
ES07h
E80Fh
ES0Fh
Intel 823715B PCI - Bus Master IDE Controller
ECP - Druckeranschluss (LPTl)
Creative Sound Blaster 16 Plug and Play
Matrox MGA Millenniun PowerDesk
Matrox MGA Millennium PowerDesk
Standard - Diskettenlaufwerk-Controller
Erster IDE Controller (Dual FIFO)
Intel 823715B PCI - Bus Master IDE Controller
COM-Anschluss (COM1)
PCI - Bus
ECP - Druckeranschluss (LPT1)
PCI-Bus
Realtek RTL8029 PCI Ethernet
Adaptec AIC - 7880 PCI SCSI Controller
Erster IDE Controller (Dual FIFO)
Intel 822715B PCI - Bus Master IDE Controller
Intel 82371SB PCI-Bus Master IDE Controller
Zweiter IDE Controller (Dual FIFO)
15.2
Adressierung der I/O Ports
Die Adressierung der I/O Ports erfolgt mit spezifischen I/O-Befehlen unter
besonderen Schutzmechanismen. Daneben besteht die Möglichkeit, durch Spiegelung des I/O Port Bereichs in den Arbeitsspeicher (memory mapped I/O) mit
den üblichen Befehlen und den üblichen Schutzmechanismen auf die I/O Ports
zuzugreifen. Nur bei Benutzung des I/O Port Adressbereichs ist jedoch garantiert, dass vor Beginn der Ausführung des folgenden Befehls der vorhergehende
abgeschlossen ist.
Zur Adressierung der I/O Ports werden die folgenden Befehle benutzt.
• Kopieren vom Port (Portadresse PORT ADR) in das Register %reg (Lesen aus Sicht des Prozessors): in PORT ADR,%reg
• Kopieren aus dem Register %reg zum Port (Portadresse PORT ADR)
(Schreiben aus Sicht des Prozessors): out %reg, PORT ADR
Liegt die I/O Port Adresse zwischen 0x0 und 0xff, kann sie als Festwert
angegeben werden, sonst über das Register dx. %reg steht für eines der Register
al, ax oder eax, je nachdem ob im I/O Port-Bereich ein Byte, ein Wort oder
ein Doppelwort ab der angegebenen Adresse PORT ADR belegt oder gelesen
werden sollen. Die Register ax oder eax enthalten die auszugebenden oder
einzulesenden Daten oder Steuerwerte.
Ähnlich den Stringbefehlen movs, cmps, ... gibt es die Befehle ins und outs,
die in Verbindung mit dem Wiederholungspräfix rep eine schnelle Übertragung
von Datenketten zwischen I/O Ports und Arbeitsspeicher ermöglichen.
15.3
Schutzmechanismen beim Zugriff auf die I/O Ports
Unter Linux arbeitet der INTEL-Prozessor im protected mode, in dem ein
Multi-Tasking-Betrieb möglich ist. Zum Schutz der unterschiedlich genutzten
Speicherbereiche existiert eine hierarchische Struktur für Speicherzugriffe, die
80
Funktion
Eingabe
ioperm
Setzt die I/Oport access permission bits
iopl
Ändert die ioplBits inm eflags
Register
eax
ebx
ecx
edx
eax
ebx
65hex = 101
Portadresse
Anzahl Byte ab Portadresse
1=Zugriff, 0=kein Zugriff
6ehex = 110
neuer Wert der iopl-Bits
(0 bis 3)
Rückgabe
Erfolg Fehler
0
<0
0
<0
Tabelle 32: SystemCalls für den Zugriff auf den I/O Adressraum. Für beide
SystemCalls ist root-Berechtigung nötig.
durch Privilegierungsniveaus (PL privilege levels) ausgedrückt wird. In Abbildung 11 ist das 4-stufige Schema des 80 ×86 Prozessors dargestellt. Programme
dürfen generell nur auf Segmente (und die darin befindlichen Prozeduren, Daten o. ä.) mit geringerem oder gleichem Privilegierungsniveau als sie selbst
zugreifen.
Der Zugriff auf den I/O Adressraum, über den die I/O Ports
erreicht werden können, ist durch die beiden Bits 12 und 13 im
eflags Register (IOPL I/O privilege level) geschützt. Er ist üblicherweise nur dem PL 0 (Kernel) und dem PL 1 (Gerätetreiber)
erlaubt. Weniger privilegierte Gerätetreiber werden abgewiesen,
Anwendungsprogramme müssen Ein- und Ausgabe durch Systemaufrufe realisieren. Die Befehle in, ins, out, outs, cli und sti
(siehe Abschnitte 13.2.1 und 13.2.2) sind I/O-sensitive Befehle. Das bedeutet, dass sie nur dann ausgeführt werden, wenn
das Privilegierungsniveau des Programms, das diese Befehle
benutzt, nicht höher als das IOPL ist. Um mit Hilfe des I/O
Adressraums auf I/O Ports zugreifen zu können, müssen zuerst
mit dem SystemCall 101 (ioperm) die I/O-port access permis- Abbildung 11: Konzept
sion bits gesetzt werden, die einen bestimmten Bereich des I/O der Privilegierungsstufen
Adressraums freigeben. Soll der über die I/O Adressen 0hex bis im Prozessor i386
3ffhex hinausgehende Bereich angesprochen werden, ist noch
nötig, den SystemCall 110 (iopl) zu benutzen.
15.4
Beispiel: Tonausgabe mit dem Systemlautsprecher
über I/O Ports
Um die Tonausgabe über den Systemlautsprecher zu verstehen, sind einige Vorbemerkungen erforderlich. Im Beispiel wird der Lautsprecher direkt über den
Zähler #2 des Timer Chips PIT 8253/8254 angesteuert. Der Timer Chip wird
über die Ports 42hex, 43hex, 61hex gesteuert. Durch den Port 43hex, der dem
Steuerregister des Timer Chips zugeordnet ist, wird einer der 3 Zähler (hier
Zähler #2) für Einstellungen ausgewählt. Durch Port 42hex wird das Zählerwort an den Zähler #2 des Timer Chips übermittelt, mit dem die Frequenz
1193,18kHz
. Zufestgelegt wird. Die gewünschte Frequenz ergibt sich als f = Zaehlerwort
81
P o rt B (6 1 h e x )
B it 7
-----
B it 2
B it 1
B it 0
Z ä h le r
0
Z ä h le r
1
C L K
Z ä h le r
2
V e rs tä rk e r
G a tte r
O U T
P IT 8 2 5 3 /8 2 5 4
Abbildung 12: Steuerung des Zählers #2 und des Gatters durch Port 61hex.
Die Auswahl und Einstellung des Zählers #2 erfolgt über die Ports 43hex und
42hex.
erst wird das niederwertige Byte, dann das höherwertige Byte an Port 42hex
übermittelt. Schließlich müssen der Zähler #2 durch Bit 0 aktiviert und der
Verstärker mit Bit 1 des Port 61hex angeschlossen werden. Insgesamt sind folgende Schritte erforderlich:
A) Die verwendeten Ports 42hex, 43hex, 61hex werden mit dem SystemCall
ioperm freigeschaltet.
B) Steuerwort zur Auswahl des Zählers #2 und für Einstellungen (Modus,
Anzahl der Zählerbytes, ... ) an Port 43hex
C) Zählerwort zur Festlegung der Frequenz wird übermittelt, zuerst das niederwertige, dann das höherwertige Byte des Zählerworts: Port 42hex
D) Einschalten des Lautsprechers: Zähler #2 wird aktiviert und der Zähler
wird mit Verstärker und Lautsprecher verbunden: Bits 0 und 1 in Port
42hex setzen (siehe Abbildung 12). Ausschalten analog durch Löschen der
Bits
E) Sperren der Ports durch 0 in edx. Geschieht automatisch wenn der Prozess
beendet ist.
.set
ioperm,101
.text
start:
.globl start
/* Systemcall #101 (ioperm): Ab Port #61hex (Systemlautsprecher)
wird 1 Byte freigegeben. */
movl
$ioperm,%eax
82
movl
movl
movl
int
$0x61,%ebx
$0x01,%ecx
$1,%edx
$0×80
/* Systemcall #101 (ioperm): Ab Port #42hex (Systemzeitgeber)
werden 2 Byte freigegeben. Im I/O Adressraum sind nun die Ports
61hex, 42hex und 43hex freigegeben. */
movl
$ioperm,%eax
movl
$0x42,%ebx
movl
$0x02,%ecx
movl
$1,%edx
int $0×80
/* Systemcall #101 (ioperm): Port #80hex wird freigegeben. Dies
wird für Festlegung der Dauer des Tons benötigt. */
movl
$ioperm,%eax
movl
$0×80,%ebx
movl
$0x01,%ecx
movl
$1,%edx
int
$0×80
/*Steuerwort (jedes Bit hat eine bestimmte Bedeutung) an
Port 43hex niederwertiges Bit des Zählerworts an Port 42hex
höherwertiges Bit des Zählerworts an Port 42hex */
go on:
movb
$0b10110110,%al
outb
%al,$0x43
movb
$0x4b,%al
outb
%al,$0x42
movb
$0x5,%al
outb
%al,$0x42
/* In Port 61hex werden die Bits 0 und 1 auf 1 gesetzt, damit
wird Zähler #2 aktiviert und mit dem Verstärker verbunden
(Einschalten des Lautsprechers) */
inb
$0x61,%al
or
$0b11,%al
outb
%al,$0x61
/* Wartezeit zur Festlegung der Tondauer */
cld
movl
$0x6ffff,%ecx
wait m2:
inb
$0×80,%al
loop
wait m2
/* Ausschalten des Lautsprechers durch Löschen der Bits 0,1 in
Port 61hex */
inb
$0x61,%al
83
and
$0b11111100,%al
outb %al,$0x61
/* Ports sperren */
movl
$ioperm,%eax
movl
$0x61,%ebx
movl
$0x01,%ecx
movl
$0,%edx
int
$0×80
/* Ports sperren */
movl
$ioperm,%eax
movl
$0x42,%ebx
movl
$0x02,%ecx
movl
$0,%edx
int
$0×80
/* Ports sperren */
movl
$ioperm,%eax
movl
$0×80,%ebx
movl
$0x01,%ecx
movl
$0,%edx
int
$0×80
movl
int
15.5
$1,%eax
$0×80
Zusammenfassung
Die Programmierung von Ports setzt genaue Kenntnisse der betroffenen peripheren Geräte voraus. Das periphere Gerät bestimmt die zugeordneten Ports
(z. B. durch Jumper auswählbar) und die Art der zu übermittelnden Informationen. Dennoch lassen sich einige allgemeine Aussagen zur Programmierung
von Ports machen.
• Aus der Gerätebeschreibung sind die Adressen der benötigten Ports und
die Daten zu entnehmen, die zur Erreichung des gewünschten Zwecks zu
übermitteln sind.
• Häufig werden unterschiedliche Ports zur Steuerung und zur Übermittlung der Betriebsdaten benutzt.
• Die Ports sind vor Inbetriebnahme mit dem SystemCall ioperm zugänglich zu machen. Dazu ist root-Berechtigung nötig.
• In manchen Fällen ist das periphere Gerät nach Übermittlung der Steuerungsund Betriebsdaten zu aktivieren (Start) und gegebenenfalls zu deaktivieren (Stop).
• Die Ports sollten nach Ende der Benutzung wieder gesperrt werden.
84
16
16.1
80 386 Dependent Features
AT&T Syntax versus Intel Syntax
In order to maintain compatibility with the output of ‘gcc’, ‘as’ supports AT&T
System V/386 assembler syntax. This is quite different from Intel syntax. We
mention these differences because almost all 80386 documents used only Intel
syntax. Notable differences between the two syntaxes are:
• AT&T immediate operands are preceded by $; Intel immediate operands
are undelimited (Intel ‘push 4’ is AT&T ‘pushl $4’). AT&T register operands are preceded by ‘%’; Intel register operands are undelimited. AT&T
absolute (as opposed to PC relative) jump/call operands are prefixed by
‘*’; they are undelimited in Intel syntax.
• AT&T and Intel syntax use the opposite order for source and destination
operands. Intel ‘add eax, 4’ is ‘addl $4, %eax’. The ‘source, dest’ convention is maintained for compatibility with previous Unix assemblers.
• In AT&T syntax the size of memory operands is determined from the last
character of the opcode name. Opcode suffixes of ‘b’, ‘w’, and ‘l’ specify
byte (8-bit), word (16-bit), and long (32-bit) memory references. Intel
syntax accomplishes this by prefixes memory operands (not the opcodes
themselves) with ‘byte ptr’, ‘word ptr’, and ‘dword ptr’. Thus, Intel ‘mov
al, byte ptr FOO’ is ‘movb FOO, %al’ in AT&T syntax.
• Immediate form long jumps and calls are ‘lcall/ljmp $SECTION, $OFFSET’ in AT&T syntax; the Intel syntax is ‘call/jmp far SECTION:OFFSET’.
Also, the far return instruction is ‘lret $STACK-ADJUST’ in AT&T syntax; Intel syntax is ‘ret far STACK-ADJUST’.
• The AT&T assembler does not provide support for multiple section programs. Unix style systems expect all programs to be single sections.
16.2
Opcode Naming
Opcode names are suffixed with one character modifiers which specify the size
of operands. The letters ‘b’, ‘w’, and ‘l’ specify byte, word, and long operands.
If no suffix is specified by an instruction and it contains no memory operands
then ‘as’ tries to fill in the missing suffix based on the destination register
operand (the last one by convention). Thus, ‘mov %ax, %bx’ is equivalent to
‘movw %ax, %bx’; also, ‘mov $1, %bx’ is equivalent to ‘movw $1, %bx’. Note
that this is incompatible with the AT&T Unix assembler which assumes that
a missing opcode suffix implies long operand size. (This incompatibility does
not affect compiler output since compilers always explicitly specify the opcode
suffix.)
Almost all opcodes have the same names in AT&T and Intel format. There
are a few exceptions. The sign extend and zero extend instructions need two
sizes to specify them. They need a size to sign/zero extend from and a size
to zero extend to. This is accomplished by using two opcode suffixes in AT&T
syntax. Base names for sign extend and zero extend are ‘movs...’ and ‘movz...’
in AT&T syntax (‘movsx’ and ‘movzx’ in Intel syntax). The opcode suffixes are
85
tacked on to this base name, the from suffix before the to suffix. Thus, ‘movsbl
%al, %edx’ is AT&T syntax for ’move sign extend from %al to %edx’. Possible
suffixes, thus, are ‘bl’ (from byte to long), ‘bw’ (from byte to word), and ‘wl’
(from word to long).
The Intel-syntax conversion instructions
•
•
•
•
‘cbw’ sign-extend byte in ‘%al’ to word in ‘%ax’,
‘cwde’ sign-extend word in ‘%ax’ to long in ‘%eax’,
‘cwd’ sign-extend word in ‘%ax’ to long in ‘%dx:%ax’,
‘cdq’
sign-extend dword in ‘%eax’ to quad in ‘%edx:%eax’,
are called ‘cbtw’, ‘cwtl’, ‘cwtd’, and ‘cltd’ in AT&T naming. ‘as’ accepts either
naming for these instructions. Far call/jump instructions are ‘lcall’ and ‘ljmp’
in AT&T syntax,but are ‘call far’ and ‘jump far’ in Intel convention.
16.3
Register Naming
Register operands are always prefixes with ‘%’. The 80386 registers consist of
• the 8 32-bit registers ‘%eax’ (the accumulator), ‘%ebx’, ‘%ecx’,‘%edx’,
‘%edi’, ‘%esi’, ‘%ebp’ (the frame pointer), and ‘%esp’(the stack pointer).
• the 8 16-bit low-ends of these: ‘%ax’, ‘%bx’, ‘%cx’, ‘%dx’, ‘%di’, ‘%si’,
‘%bp’, and ‘%sp’.
• the 8 8-bit registers: ‘%ah’, ‘%al’, ‘%bh’, ‘%bl’, ‘%ch’, ‘%cl’, ‘%dh’, and
‘%dl’ (These are the high-bytes and low-bytes of ‘%ax’, ‘%bx’, ‘%cx’, and
‘%dx’)
• the 6 section registers ‘%cs’ (code section), ‘%ds’ (data section), ‘%ss’
(stack section), ‘%es’, ‘%fs’, and ‘%gs’.
• the 3 processor control registers ‘%cr0’, ‘%cr2’, and ‘%cr3’.
• the 6 debug registers ‘%db0’, ‘%db1’, ‘%db2’, ‘%db3’, ‘%db6’, and ‘%db7’.
• the 2 test registers ‘%tr6’ and ‘%tr7’.
• the 8 floating point register stack ‘%st’ or equivalently ‘%st(0)’, ‘%st(1)’,
‘%st(2)’, ‘%st(3)’, ‘%st(4)’, ‘%st(5)’, ‘%st(6)’, and ‘%st(7)’.
16.4
Opcode Prefixes
Opcode prefixes are used to modify the following opcode. They are used to
repeat string instructions, to provide section overrides, to perform bus lock
operations, and to give operand and address size (16-bit operands are specified
in an instruction by prefixing what would normally be 32-bit operands with a
’operand size’ opcode prefix). Opcode prefixes are usually given as single-line
instructions with no operands, and must directly precede the instruction they
act upon. For example, the ‘scas’ (scan string) instruction is repeated with:
repne
scas
86
Here is a list of opcode prefixes:
• Section override prefixes ‘cs’, ‘ds’, ‘ss’, ‘es’, ‘fs’, ‘gs’. These are automatically added by specifying using the SECTION:MEMORY-OPERAND
form for memory references.
• Operand/Address size prefixes ‘data16’ and ‘addr16’ change 32-bit operands/addresses into 16-bit operands/addresses. Note that 16-bit addressing modes (i.e. 8086 and 80286 addressing modes) are not supported
(yet).
• The bus lock prefix ‘lock’ inhibits interrupts during execution of the instruction it precedes. (This is only valid with certain instructions; see a
80386 manual for details).
• The wait for coprocessor prefix ‘wait’ waits for the coprocessor to complete the current instruction. This should never be needed for the 80386/80387
combination.
• The ‘rep’, ‘repe’, and ‘repne’ prefixes are added to string instructions to
make them repeat ‘%ecx’ times.
16.5
Memory References
An Intel syntax indirect memory reference of the form
SECTION:[BASE + INDEX*SCALE + DISP]
is translated into the AT&T syntax
section:disp(base, index, scale),
where BASE and INDEX are the optional 32-bit base and index registers,
DISP is the optional displacement, and SCALE, taking the values 1, 2, and
8, multiplies INDEX to calculate the address of the operand. If no SCALE
is specified, SCALE is taken to be 1. SECTION specifies the optional section
register for the memory operand, and may override the default section register
(see a 80386 manual for section register defaults). Note that section overrides
in AT&T syntax must have be preceded by a ‘%’. If you specify a section
override which coincides with the default section register, ‘as’ does not output
any section register override prefixes to assemble the given instruction. Thus,
section overrides can be specified to emphasize which section register is used
for a given memory operand. Here are some examples of Intel and AT&T style
memory references:
AT&T: ‘-4(%ebp)’, Intel: ‘[ebp - 4]’
base is ‘%ebp’; DISP is ‘-4’. SECTION is missing, and the default section
is used (‘%ss’ for addressing with ‘%ebp’ as the base register). INDEX,
SCALE are both missing.
AT&T: ‘foo(,%eax,4)’, Intel: ‘[foo + eax*4]’
INDEX is ‘%eax’ (scaled by a SCALE 4); DISP is ‘foo’. All other fields
are missing. The section register here defaults to ‘%ds’.
AT&T: ‘foo(,1)’; Intel ‘[foo]’
87
This uses the value pointed to by ‘foo’ as a memory operand. Note that
BASE and INDEX are both missing, but there is only one ‘,’. This is a
syntactic exception.
AT&T: ‘%gs:foo’; Intel ‘gs:foo’
This selects the contents of the variable ‘foo’ with section register SECTION being ‘%gs’.
Absolute (as opposed to PC relative) call and jump operands must be prefixed with ‘*’. If no ‘*’ is specified, ‘as’ always chooses PC relative addressing
for jump/call labels. Any instruction that has a memory operand must specify
its size (byte, word, or long) with an opcode suffix (‘b’, ‘w’, or ‘l’, respectively).
16.6
Handling of Jump Instructions
Jump instructions are always optimized to use the smallest possible displacements. This is accomplished by using byte (8-bit) displacement jumps whenever
the target is sufficiently close. If a byte displacement is insufficient a long (32bit) displacement is used. We do not support word (16-bit) displacement jumps
(i.e. prefixing the jump instruction with the ‘addr16’ opcode prefix), since the
80386 insists upon masking ‘%eip’ to 16 bits after the word displacement is
added. Note that the ‘jcxz’, ‘jecxz’, ‘loop’, ‘loopz’, ‘loope’, ‘loopnz’ and ‘loopne’ instructions only come in byte displacements, so that if you use these
instructions (‘gcc’ does not use them) you may get an error message (and incorrect code). The AT&T 80386 assembler tries to get around this problem by
expanding ‘jcxz foo’ to
jcxz cx zero
jmp cx nonzero
cx zero: jmp foo
cx nonzero:
16.7
Floating Point
All 80387 floating point types except packed BCD are supported. (BCD support
may be added without much difficulty). These data types are 16-, 32-, and 64bit integers, and single (32-bit), double (64-bit), and extended (80-bit) precision
floating point. Each supported type has an opcode suffix and a constructor
associated with it. Opcode suffixes specify operand’s data types. Constructors
build these data types into memory.
• Floating point constructors are ‘.float’ or ‘.single’, ‘.double’, and ‘.tfloat’
for 32-, 64-, and 80-bit formats. These correspond to opcode suffixes ‘s’,
‘l’, and ‘t’. ‘t’ stands for temporary real, and that the 80387 only supports
this format via the ‘fldt’ (load temporary real to stack top) and ‘fstpt’
(store temporary real and pop stack) instructions.
• Integer constructors are ‘.word’, ‘.long’ or ‘.int’, and ‘.quad’ for the 16-,
32-, and 64-bit integer formats. The corresponding opcode suffixes are ‘s’
(single), ‘l’ (long), and ‘q’ (quad). As with the temporary real format the
64-bit ‘q’ format is only present in the ‘fildq’ (load quad integer to stack
top) and ‘fistpq’ (store quad integer and pop stack) instructions.
88
Register to register operations do not require opcode suffixes, so that ‘fst
%st, %st(1)’ is equivalent to ‘fstl %st, %st(1)’. Since the 80387 automatically
synchronizes with the 80386 ‘fwait’ instructions are almost never needed (this
is not the case for the and 8086/8087 combinations). Therefore, ‘as’ suppresses
the ‘fwait’ instruction whenever it is implicitly selected by one of the ‘fn...’
instructions. For example, ‘fsave’ and ‘fnsave’ are treated identically. In general,
all the ‘fn...’ instructions are made equivalent to ‘f...’ instructions. If ‘fwait’ is
desired it must be explicitly coded.
16.8
Writing 16-bit Code
While as normally writes only “pure” 32-bit i386 code, it has limited support
for writing code to run in real mode or in 16-bit protected mode code segments.
To do this, insert a ‘.code16’ directive before the assembly language instructions
to be run in 16-bit mode. You can switch GAS back to writing normal 32-bit
code with the ‘.code32’ directive. GAS understands exactly the same assembly
language syntax in 16-bit mode as in 32-bit mode. The function of any given
instruction is exactly the same regardless of mode, as long as the resulting
object code is executed in the mode for which GAS wrote it. So, for example,
the ‘ret’ mnemonic produces a 32-bit return instruction regardless of whether
it is to be run in 16-bit or 32-bit mode. (If GAS is in 16-bit mode, it will add
an operand size prefix to the instruction to force it to be a 32-bit return.) This
means, for one thing, that you can use GNU CC to write code to be run in real
mode or 16-bit protected mode. Just insert the statement ‘asm(“.code16”);’ at
the beginning of your C source file, and while GNU CC will still be generating
32-bit code, GAS will automatically add all the necessary size prefixes to make
that code run in 16-bit mode. Of course, since GNU CC only writes small-model
code (it doesn’t know how to attach segment selectors to pointers like native
×86 compilers do), any 16-bit code you write with GNU CC will essentially be
limited to a 64K address space. Also, there will be a code size and performance
penalty due to all the extra address and operand size prefixes GAS has to add
to the instructions. Note that placing GAS in 16-bit mode does not mean that
the resulting code will necessarily run on a 16-bit pre-80386 processor. To write
code that runs on such a processor, you would have to refrain from using any 32bit constructs which require GAS to output address or operand size prefixes. At
the moment this would be rather difficult, because GAS currently supports only
32-bit addressing modes: when writing 16-bit code, it always outputs address
size prefixes for any instruction that uses a non-register addressing mode. So
you can write code that runs on 16-bit processors, but only if that code never
references memory.
16.9
Notes
There is some trickery concerning the ‘mul’ and ‘imul’ instructions that deserves
mention. The 16-, 32-, and 64-bit expanding multiplies (base opcode ‘0xf6’;
extension 4 for ‘mul’ and 5 for ‘imul’) can be output only in the one operand
form. Thus, ‘imul %ebx, %eax’ does not select the expanding multiply; the
expanding multiply would clobber the ‘%edx’ register, and this would confuse
‘gcc’ output. Use ‘imul %ebx’ to get the 64-bit product in ‘%edx:%eax’. We
have added a two operand form of ‘imul’ when the first operand is an immediate
89
mode expression and the second operand is a register. This is just a shorthand,
so that, multiplying ‘%eax’ by 69, for example, can be done with ‘imul $69,
%eax’ rather than ‘imul $69, %eax, %eax’.
90
17
Direktiven
Direktiven (auch Pseudooperationen genannt) sind Instruktionen an den Assembler, keine Instruktionen für den Prozessor. Sie geben dem Programmierer
Kontrolle über die Arbeitsweise des Assemblers und beginnen alle mit dem
Punkt (.). Üblicherweise werden Kleinbuchstaben verwendet.
• .code32 erzeugt 32-bit Code
• .text SUBSECTION übersetzt die folgenden Anweisungen an das Ende
der betreffenden SUBSECTION (SUBSECTION ist ein absoluter Ausdruck, wird er weggelassen, so wird autom. 0 ergänzt). Dieser Abschnitt
enthält Prozessorbefehle und Konstanten; Bytes in diesem Abschnitt können
während der Programmausführung nicht überschrieben werden!
• .global SYMBOL bzw. .globl SYMBOL macht das SYMBOL sichtbar für
den Linker ld. Damit kann der Wert des SYMBOLs für andere Programme zugänglich gemacht werden, die mit ihm zusammengebunden werden.
• .data SUBSECTION wie .text, nur sind Bytes in diesem Abschnitt während
der Programmausführung änderbar
• .ascii “STRING” erwartet 0, eine oder mehrere Zeichenketten, die durch
Kommata getrennt sind. Diese werden in fortlaufende Adressen abgelegt.
• .asciz “STRING” wie .ascii, jedoch wird am Ende noch eine binäre Null
angehängt.
• .long EXPRESSION erwartet 0, eine oder mehrere EXPRESSIONs, die
durch Kommata getrennt sind. Der Wert des Ausdrucks wird in jeweils
4 bytes abgelegt.
• .int EXPRESSION identisch mit .long
• .space SIZE, FILL oder .skip SIZE, FILL reserviert SIZE Bytes und Initialisiert sie mit dem Wert FILL.
• .fill REPEAT, SIZE, VALUE erzeugt REPEAT Kopien von SIZE Bytes mit dem Wert VALUE. REPEAT, SIZE und VALUE sind absolute
Ausdrücke. SIZE und VALUE sind optional.
• .if ABSOLUTE.EXPRESSION markiert einen Programmteil, der nur
dann als Teil des Quellprogramms betrachtet wird, wenn ABSOLUTE.EXPRESSION
ungleich Null ist.
• .endif markiert das Ende von .if
• .else markiert den Alternativzweig von .if; markiert den Beginn von Anweisungen, die assembliert werden, wenn die Bedingung für das vorangegangene .if falsch war
• .macro und .endm gestatten die Definition von Macros, die AssemblerAusgaben (textuelle Substitution) erzeugen. .macro MACRONAME bzw.
.macro MACRONAME MACROARGS Falls Argumente erforderlich sind,
91
werden diese hinter dem Namen durch Kommata oder Leerstellen voneinander getrennt. Defaultwerte können durch = WERT unmittelbar hinter
dem Namen mitgegeben werden. Innerhalb des Macros werden die Argumente durch Voranstellen von \ vor den Argumentnamen ausgewertet.
Beispiel:
.macro RPT from=0, to=3
.long \from
.if \to-\from
RPT “(\from+1)” \to
.endif
.endm
RPT erzeugt:
.long
.long
.long
.long
0
1
2
3
RPT 2 7 oder RPT 2,7 erzeugt eine entsprechende Ausgabe .long 2 bis
.long 7. RPT 2,7 ist gleichwertig mit RPT to=7, from=2. RPT ,7 dagegen
beginnt mit dem Defaultwert 0 und endet bei 7.
• .set NAME,EXPRESSION .equ NAME,EXPRESSION .equiv NAME,EXPRESSION
setzt den Namen NAME dem Ausruck EXPRESSION gleich. Mit .set
WRITE,4 kann z. B. bei Benutzung des Interrupts 0×80 zur Ausgabe
einer Zeichenkette geschrieben werden: movl $WRITE, %eax.
• .byte EXPRESSIONS oder .word EXPRESSIONS oder .long EXPRESSIONS erwartet 0 oder mehr Ausdrücke, getrennt durch Kommata. Für
jeden Ausdruck wird die entsprechende Anzahl von Bytes fortlaufend reserviert und mit den Ausdrücken belegt.
• .include ’PFAD/DATEI’ Die angegeben Datei wird assembliert und an
dieser Stelle eingefügt, dann wird der Rest der Quelldatei assembliert.
• .org NEUER LOCATION COUNTER, FILL erhöht den Location Counter auf den angegebenen Wert und füllt mit FILL auf. Überschreiten der
Segmentgrenzen ist nicht möglich.
Im folgenden ist eine vollständige Liste der Direktiven aufgeführt, die unabhängig von der speziellen Rechnerkonfiguration für den GNU Assembler zur
Verfügung stehen.
Abort
ABORT
Align
App-File
Ascii
Asciz
Balign
Byte
Comm
Data
.abort
.ABORT
.align ABS-EXPR,ABS-EXPR
.app-file STRING
.ascii “STRING”
.asciz “STRING”
.balign ABS-EXPR,ABS-EXPR
.byte EXPRESSIONS
.comm SYMBOL,LENGTH
.data SUBSECTION
92
Def
Desc
Dim
Double
Eject
Else
Endef
Endif
Equ
Equiv
Err
Extern
File
Fill
Float
Global
hword
Ident
If
Include
Int
Irp
Irpc
Lcomm
Lflags
Line
Ln
Linkonce
List
Long
Macro
MRI
Nolist
Octa
Org
P2align
Psize
Quad
Rept
Sbttl
Scl
Section
Set
Short
Single
Size
Skip
Space
Stab
String
.def NAME
.desc SYMBOL,ABS-EXPRESSION
.dim
.double FLONUMS
.eject
.else
.endef
.endif
.equ SYMBOL,EXPRESSION
.equiv SYMBOL,EXPRESSION
.err
.extern
.file STRING
.fill REPEAT,SIZE,VALUE
.float FLONUMS
.global SYMBOL, .globl SYMBOL
.hword EXPRESSIONS
.ident
.if ABSOLUTE EXPRESSION
.include “FILE”
.int EXPRESSIONS
.irp SYMBOL,VALUES
.irpc SYMBOL,VALUES
.lcomm SYMBOL , LENGTH
.lflags
.line LINE-NUMBER
.ln LINE-NUMBER
.linkonce [TYPE]
.list
.long EXPRESSIONS
.macro NAME ARGS
.mri VAL
.nolist
.octa BIGNUMS
.org NEW-LC,FILL
.p2align ABS-EXPR,ABS-EXPR
.psize LINES,COLUMNS
.quad BIGNUMS
.rept COUNT
.sbttl “SUBHEADING”
.scl CLASS
.section NAME, SUBSECTION
.set SYMBOL, EXPRESSION
.short EXPRESSIONS
.single FLONUMS
.size
.skip SIZE,FILL
.space SIZE,FILL
.stabd, .stabn, .stabs
.string “STR”
93
Symver
Tag
Text
Title
Type
Val
Word
.symver NAME,NAME2@NODENAME
.tag STRUCTNAME
.text SUBSECTION
.title “HEADING”
.type INT
.val ADDR
.word EXPRESSIONS
94
18
Systemfunktionen
Systemfunktionen sind Prozeduren, die vom Betriebssystem dem Programmierer zur Verfügung gestellt werden. Sie sind als Software-Interrupts (siehe Abschnitt ?? programmiert. Für den Benutzer ist zunächst nur wichtig, dass sie
wie Prozeduren benutzt werden können, lediglich die Art des Aufrufs und die
Parameterübergabe weicht von der bei Prozeduren üblichen Art ab. Statt call
PROZNAME lautet der Aufruf int NUMMER, die Parameterübergabe findet nur
über die Register eax, ebx, ecx, edx statt.
Die Tabelle 33 enthält die für den wichtigen Software-Interrupt 0×80 vorhandenen Funktionen. Der Software-Interrupt 0×80 ermöglicht den Zugang zu
einer Sammlung von Funktionen, die einzeln nummeriert sind (siehe Tabelle 33). Die Nummer der gewünschten Funktion muss im Register eax stehen.
Wenn z. B. 8 im Register eax enthalten ist (movl $8,%eax) und der Interrupt
80hex ausgelöst wird (int $0×80) wird die Prozedur creat (Erzeugen einer
Datei) gestartet. In vielen Fällen sind zusätzliche Informationen nötig. Welche
nötig sind, kann den Manpages entnommen werden.
Beispiel: Sie wollen die Prozedur write (Ausgabe einer Kette von Bytes)
benutzen. Mit man write und/oder man 2 write erhalten Sie die Beschreibung
der Prozedur write in der für C-Programmierer gedachten Schreibweise:
size t write (int fd, const void *buf, size t count)
↑
↑
↑
↑
eax
ebx
ecx
edx
Die gezeigte Zuordnung gilt in der angegebenen Reihenfolge für alle SystemCalls. Eax enthält immer die Information über die Prozedur (z. B. 4 für
write). Die nachfolgenden Parameter in der Klammer werden in dieser Reihenfolge auf die Register ebx, ecx und edx bezogen. Rückgabewerte, die über
die Ausführung der Funktion informieren, werden in eax zurückgegeben. In
manchen Fällen werden nicht alle Register benötigt. Im genannten Fall (write)
enthalten die Register den Adressaten der Ausgabe (Monitor, Drucker, Datei
(ebx), die Anfangsadresse des Puffers (ecx)(der Puffer enthält die auszugebenden Bytes) und die Länge des Puffers (edx). Der Rückgabewert in eax lautet
bei aufgetretenen Fehlern -1, ansonsten enthält eax die Anzahl der tatsächlich
ausgegebenen Bytes.
95
Name
setup
exit
fork
read
write
open
close
waitpid
creat
link
unlink
execve
chdir
time
mknod
chmod
chown
break
oldstat
lseek
getpid
mount
umount
setuid
getuid
stime
ptrace
alarm
oldfstat
pause
utime
stty
gtty
access
nice
ftime
sync
kill
rename
mkdir
rmdir
Nr
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Name
dup
pipe
times
prof
brk
setgid
getgid
signal
geteuid
getegid
acct
phys
lock
ioctl
fcntl
mpx
setpgid
ulimit
oldolduname
umask
chroot
ustat
dup2
getppid
getpgrp
setsid
sigaction
sgetmask
ssetmask
setreuid
setregid
sigsuspend
sigpending
sethostname
setrlimit
getrlimit
getrusage
gettimeofday
settimeofday
getgroups
setgroups
Nr
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
Name
select
munmap
truncate
ftruncate
fchmod
fchown
symlink
oldlstat
readlink
uselib
swapon
reboot
readdir
mmap
getpriority
setpriority
profil
statfs
fstatfs
ioperm
socketcall
syslog
setitimer
getitimer
stat
lstat
fstat
olduname
iopl
vhangup
idle
vm86
wait4
swapoff
sysinfo
ipc
fsync
sigreturn
clone
setdomainname
uname
Nr
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
Name
modify ldt
adjtimex
mprotect
sigprocmask
create module
init module
delete module
get kernel syms
quotactl
getpgid
fchdir
bdflush
sysfs
personality
afs syscall
setfsuid
setfsgid
llseek
getdents
newselect
flock
msync
readv
writev
getsid
fdatasync
sysctl
mlock
munlock
mlockall
munlockall
sched setparam
sched getparam
sched setscheduler
sched getscheduler
sched yield
sched get priority max
sched get priority min
sched rr get interval
nanosleep
mremap
Tabelle 33: Liste der SystemCalls unter dem Interrupt 0×80
96
Nr
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
Literatur
[1] ELSNER, D., FENLASON J. & friends: The GNU Assembler, 1994
http://www.gnu.org/manual/gas-2.9.1/html mono/as.html/
http://www.gnu.org/manual/gas/html mono/as.html,
http://www.gnu.org/manual/gas-2.9.1/html node/as toc.html,
http://www.gnu.org/manual/gas/html node/as toc.html,
http://www.gnu.org/manual/gas-2.9.1/html chapter/as toc.html,
http://www.gnu.org/manual/gas/html chapter/as toc.html,
(Stand August 2001)
/usr/info/as.info.gz, man as
[2] http://developer.intel.com/design/PentiumIII/manuals/index.htm
[3] WOHAK, B., MAURUS, R.: 80×86/Pentuim Assembler Programmierung
unter DOS und Windows. Bonn; Albany: IWT Verlag GmbH 1995
[4] BRADLEY, D.: Programmieren in Assembler. München: Carl Hanser Verlag 1986
[5] BRORS, I.: Maschinensprache des IBM-PC/AT und kompatibler in der
Praxis. Heidelberg: Hüthig Verlag 1988
[6] HAHN, H.: Assembler Inside & Out. Berkeley: McGraw Hill 1992
[7] LINK, W.: Assembler-Programmierung. Poing: Francis-Verlag 1995
[8] MATTHES, W.: Intel’s i486. Aachen: Elektor-Verlag 1992
[9] MORGAN, D.: Numerical Methods Real-Time and Embedded Systems
Programming. San Mateo: M&T Publ., Prentice Hall 1992
[10] NORTON, P., SOCHA, J.: Peter Norton’s Assemblerbuch. Haar b. Mchn.:
Markt & Technik Verlag 1988
[11] PODSCHUN, T.E.: Das Assembler-Buch. Bonn: Addison-Wesley 1994
[12] THIES, K.-D.: 80486 Systemsoftware-Entwicklung. München: Hanser Verlag 1992
[13] WENDLER, F.: C und Assembler in der Systemprogrammierung. Würzburg: Vogel-Verlag 1992
Motorola
[14] FORD, W., TOPP, W.: MC68000 Assembly Language and Systems Programming. Lexington: Heath and Co. 1988
[15] SKINNER, T.P.: Assembly Language Programming for the 68000 Family.
New York: Wiley 1988
Sonstige
[16] MESSMER, H.-P: PC-Hardware. Bonn: Addison Wesley 1998
[17] BREUER H., München: dtv-Atlas zur Informatik, Tafeln und Texte (Band
2490) Deutscher Taschenbuch Verlag
97
[18] SCHNEIDER U., WERNER D., München Wien: Taschenbuch der Informatik, Fachbuchverlag Leipzig im Carl Hanser Verlag 2000
[19] JACOBSON, E.: Einführung in die Prozessdatenverarbeitung. München,
Wien: Carl Hanser Verlag 1996
[20] DUMBACHER, B.: Grundlagen der Informatik, Manuskript zur Vorlesung
[21] FLIK, T., LIEBIG, H.: Mikroprozessor-Technik. Berlin: Springer-Verlag
1994
[22] KAMMERER, P.: Von Pascal zu Assembler. Braunschweig: Vieweg Verlag
1998
[23] SCHREINER, A.-T.: Systemprogrammierung in UNIX. Stuttgart: Teubner Verlag 1984
98
Anhang: Tabelle der Ascii-Zeichencodes
S C H L Ü S S E L
B 6
B 5
0
B 3
B 2
B 1
B 4
B 0
0
0
0
0
0
0
0
0
0
0
1
1
1
1 2
0
1
1
A
1 3
1
1
B
1
0
1 4
0
1
C
1
1
1 5
0
1
1
9
1
0
D
1
0
1 6
1
1
7
1 1
1
0
7
8
0
1
A C K
1 0
0
0
6
1
E
1 7
F
E O T
E N Q
6
1
0
1
5
0
1
0
4
5
1
E T X
4
1
1
0
3
0
0
2 0
0
1 0
2 1
1
1 1
2 2
2
1 2
2 3
3
1 3
2 4
S T X
3
1
0
1
2
B E L
B S
H T
L F
V T
F F
C R
S O
S I
0
0
S O H
1
2
1
1
1
0
0
0
N U L
1
1
0
0
0
0
0
0
1 4
2 5
1 5
2 6
1 6
2 7
1 7
3 0
1 8
3 1
1 9
3 2
1 A
3 3
1 B
3 4
1 C
3 5
1 D
3 6
1 E
3 7
1 F
D L E
D C 1
D C 2
D C 3
D C 4
N A K
S Y N
E T B
C A N
E M
S U B
E S C
F S
G S
R S
U S
0
1
4 0
1 6
2 0
4 1
1 7
2 1
4 2
1 8
2 2
4 3
1 9
2 3
4 4
2 0
2 4
4 5
2 1
2 5
4 6
2 2
2 6
4 7
2 3
2 7
5 0
2 4
2 8
5 1
2 5
2 9
5 2
2 6
2 A
5 3
2 7
2 B
5 4
2 8
2 C
5 5
2 9
2 D
5 6
3 0
2 E
5 7
3 1
2 F
0
1
1
0
S P
!
"
#
$
%
&
´
(
)
*
+
,
.
/
6 0
3 2
3 0
6 1
3 3
3 1
6 2
3 4
3 2
6 3
3 5
3 3
6 4
3 6
3 4
6 5
3 7
3 5
6 6
3 8
3 6
6 7
3 9
3 7
7 0
4 0
3 8
7 1
4 1
3 9
7 2
4 2
3 A
7 3
4 3
3 B
7 4
4 4
3 C
7 5
4 5
3 D
7 6
4 6
3 E
7 7
4 7
3 F
99
1
2
3
4
5
6
7
8
9
:
;
=
?
>
0
1
8 0
0
<
1
4 8
4 0
8 1
4 9
4 1
8 2
5 0
4 2
8 3
5 1
4 3
8 4
5 2
4 4
8 5
5 3
4 5
8 6
5 4
4 6
8 7
5 5
4 7
9 0
5 6
4 8
9 1
5 7
4 9
9 2
5 8
4 A
9 3
5 9
4 B
9 4
6 0
4 C
9 5
6 1
4 D
9 6
6 2
4 E
9 7
6 3
4 F
o c ta l
2 5
h e x
1 5
N A K
1
0
0
1 0 0
@
A
B
C
D
E
F
G
H
I
J
K
L
M
O
N
6 4
5 0
1 0 1
6 5
5 1
1 0 2
6 6
5 2
1 0 3
6 7
5 3
1 0 4
6 8
5 4
1 0 5
6 9
5 5
1 0 6
7 0
5 6
1 0 7
7 1
5 7
1 1 0
7 2
5 8
1 1 1
7 3
5 9
1 1 2
7 4
5 A
1 1 3
7 5
5 B
1 1 4
7 6
5 C
1 1 5
7 7
5 D
1 1 6
7 8
5 E
1 1 7
7 9
5 F
2 1
d e c im a l
1
1
1 2 0
P
Q
R
S
T
U
V
W
X
Y
Z
[
\
]
^
_
8 0
6 0
8 1
6 1
1 2 2
8 2
6 2
1 2 3
8 3
6 3
1 2 4
8 4
6 4
1 2 5
8 5
6 5
1 2 6
8 6
6 6
1 2 7
8 7
6 7
1 3 0
8 8
6 8
1 3 1
8 9
6 9
1 3 2
9 0
6 A
1 3 3
9 1
6 B
1 3 4
9 2
6 C
1 3 5
9 3
6 D
1 3 6
9 4
6 E
1 3 7
9 5
6 F
1
1
1 4 0
`
a
b
c
d
e
f
g
h
i
j
k
l
m
o
n
1
0
9 6
7 0
1 4 1
9 7
7 1
1 4 2
9 8
7 2
1 4 3
9 9
7 3
1 4 4
1 0 0
7 4
1 4 5
1 0 1
7 5
1 4 6
1 0 2
7 6
1 4 7
1 0 3
7 7
1 5 0
1 0 4
7 8
1 5 1
1 0 5
7 9
1 5 2
1 0 6
7 A
1 5 3
1 0 7
7 B
1 5 4
1 0 8
7 C
1 5 5
1 0 9
7 D
1 5 6
1 1 0
7 E
1 5 7
1 1 1
7 F
1
p
1 1 2
q
1 1 3
r
1 1 4
s
1 1 5
t
1 1 6
u
1 1 7
v
1 1 8
w
1 1 9
x
1 2 0
y
1 2 1
z
1 2 2
{
|
}
~
D E L
1 2 3
1 2 4
1 2 5
1 2 6
1 2 7
Herunterladen