Assemblerprogrammierung
Ein Assembler ist ein Programm, welches Assemblersprache in Maschinensprache (1110001) übersetzt. Häufig wird jedoch Assemblersprache durch "Assembler" abgekürzt. Die Assemblersprache zählt allerdings nicht zu den hohen Programmiersprachen wie C, C++ oder Java.
Die meisten Programme werden in höheren Programmiersprachen geschrieben, da diese für uns leichter zu verstehen ist. Allerdings müssen sie durch einen Compiler übersetzt werden. Assemblerprogrammierung ist sehr hardwarenahe und wird deshalb sehr schnell ausgeführt.
Häufig werden komplexe Algorithmen mit schlechter Laufzeit in Assembler oder C ausgelagert, und im Nachhinein z.B. in Java eingebunden.
x86 Prozessor-Architektur
Zunächst muss man sich mit der Architektur und dem Befehlssatz von x86-Prozessoren auseinandersetzen. Es lassen sich nämlich alle CPU`s in einem zum 8086-Prozessor kompatiblen Modus betreiben. Deshalb ist ein gutes Verständnis von Assembler und der darunterliegenden Prozessorarchitektur notwendig. Außerdem sollte man die Arbeitsweise des Stacks und der Register kennen.
Interrupts
Ein Programm kann beim Eintreffen eines bestimmten internen oder externen Ereignisses unterbrochen werden. Ein Interrupt unterbricht das laufende Programm, beendet den aktuellen Maschinenbefehl und ruft ein spezielles Unterprogramm auf (ISR: Interrupt Service Routine). Anschließend erfolgt der Rücksprung aus der Interrupt Service Routine und das Programm wird fortgeführt.
Register - interne Speicherstellen
Register sind interne Speicherstellen, die direkt bei der CPU liegen und Ergebnisse und Berechnungen aufnehmen können. Fast alle CPU Operationen funktionieren nur mit Registern. Man hat mit Assembler direkten Zugriff darauf.
Alle x86 Prozessoren haben 14 Register die jeweils 16 Bit groß sind. 8 Registernamen beginnen mit einem E (extended), da diese von 16 auf 32 Bit erweitert wurden. Die unteren 16 Bit dieser Register können explizit mit den alten 16 Bit Registern angesprochen werden.
4 dieser Register (AX, BX, CX, DX), auch Universalregister genannt, haben zusätzlich noch eine Besonderheit. Jedes dieser Register lässt sich in ein High Byte und ein Low Byte aufteilen, also zu 2 Register je 8 Bit groß.
Zudem gibt es Index- und Zeigerregister (SI, DI, SP, BP) und die Segmentregister (CS, DS, SS, ES).
Bit | 0 | 7 | 15 | 31 | ||
Akkumulator | AL | EAX | AH | |||
Base-Register | BL | EBX | BH | |||
Count-Register | CL | ECX | CH | |||
Data-Register | DL | EDX | DH | |||
Source Index | ESI | |||||
Destination Index | EDI | |||||
Stackpointer | ESP | |||||
Basepointer | EBP | |||||
Codesegment-Register | CS | |||||
Datasegment-Register | DS | |||||
Stacksegment-Register | SS | |||||
Extrasegment-Register | ES | |||||
Instruction Pointer | IP | |||||
Flags | Flags |
Stapel
Bevor man sich mit der eigentlichen Assemblerprogrammierung beschäftigt, muss man sich zunächst mit dem Stack vertraut machen.
Der Stack (Stapel), ist ein bestimmter Abschnitt im Hauptspeicher, der nach dem LIFO (Last In First Out) Verfahren arbeitet. Daten die zuletzt auf den Stack gelegt wurden, werden als erstes wieder entfernt. Er kann mit Hilfe seiner Operationen PUSH und POP direkt benutzt werden. PUSH legt Daten auf dem Stack ab, POP holt sie wieder herunter. Der Stack ist ein wichtiger, elementarer Bestandteil, der sehr schnell arbeitet, da es extra reservierte Register dafür gibt. Diese sind zum einen das Stacksegment (SS) und zum anderen der Stackpointer (SP). Bei den Operationen PUSH und POP wird die automatische Stackverwaltung durch die Register SS und SP benutzt.
Wichtige Maschinenbefehle
Move-Befehl
mov <Zieloperand, Quelloperand>
der move-Befehl ist einer der am häufigsten verwendeten Assembler Befehle und kopiert den Quelloperanden in den Zieloperanden. Hierbei ist zu beachten, dass beide Operatoren die gleiche Größe besitzen müssen. Des Weiteren kann keine Speicherstelle in eine andere kopiert werden. Das gilt allerdings für alle Assemblerbefehle.
Exchange-Befehl
xchg <Zieloperand, Quelloperand>
Mit Hilfe des exchange-Befehls können 2 Operanden vertauscht werden.
Addition/addition with carry-Befehl
add/adc <Zieloperand, Quelloperand>
Diese beiden Befehle addieren Ziel- und Quelloperand und speichern das Ergebnis im Zieloperand ab. Der adc-Befehl berücksichtigt allerdings das Carry Flag. Dieses wird gesetzt, wenn ein Bit-Übertrag vorhanden ist.
Subtract/subtract with borrow-Befehl
sub/sbb <Zieloperand, Quelloperand>
Diese beiden Befehle ziehen den Quelloperand vom Zieloperand ab und speichern es im Zieloperand.
Verändern des Carry-Flags
Um das Carry Flag zu verändern, stehen dem Programmierer 3 Befehle zu Verfügung.
Zum Setzen des Carry Flags:
stc (Set Carry Flag)
Zum löschen des Carry Flags:
clc (Clear Carry Flag)
Um den Zustand des Carry Flags umzudrehen:
cmc (Complement Carry Flag)
Der increment/decrement-Befehl
inc/dec Zieloperand
Durch diese beiden Befehle kann der Zieloperand um eins erhöht, bzw. um eins verringert werden. Das erreicht man auch durch die Befehle add/sub, allerdings wird beim inc/dec Befehl das Carry Flag nicht beeinflusst.
Der multiply unsigned/integer multiply-Befehl
mul/imul <Zielperand>
Der multiply-Befehl multipliziert den Zieloperanden mit dem Register AL, AX oder EAX. Der imul-Befehl multipiziert dabei eine vorzeichenbeahftete Zahl.
Der interrupt-Befehl
int <InterruptNummer>
Der multiply-Befehl multipliziert den Zieloperanden mit dem Register AL, AX oder EAX. Der imul-Befehl multipiziert dabei eine vorzeichenbeahftete Zahl.
Assemblerprogramme
Um Assemblerprogramme ausführen zu können, benötigst du:
- Turbo Assembler (https://sourceforge.net/projects/tasmeditor/)
- DOSBox: (https://www.chip.de/downloads/DOSBox_13015039.html)
Eine Installations- und Initialisierungsanleitung findest du hier.
Hello World
title Hello World Program
dosseg
.model small
.stack 100h
.data
hello_message db 'Hello, World!',0dh,0ah,'$'
.code
main proc
mov ax,@data
mov ds,ax
mov ah,9
mov dx,offset hello_message
int 21h
mov ax,4C00h
int 21h
main endp
end main
TASM - Turbo AssemblerEinrichtung und Installation des TASM - Turbo Assemblers | keyboard_arrow_right |