Montage - Kurzanleitung
Was ist Assemblersprache?
Jeder Personal Computer verfügt über einen Mikroprozessor, der die arithmetischen, logischen und Steuerungsaktivitäten des Computers verwaltet.
Jede Prozessorfamilie verfügt über eigene Anweisungen zum Behandeln verschiedener Vorgänge, z. B. zum Abrufen von Eingaben über die Tastatur, zum Anzeigen von Informationen auf dem Bildschirm und zum Ausführen verschiedener anderer Aufgaben. Diese Anweisungen werden als "Maschinensprachenanweisungen" bezeichnet.
Ein Prozessor versteht nur Anweisungen in Maschinensprache, die Zeichenfolgen von Einsen und Nullen sind. Die Maschinensprache ist jedoch zu dunkel und komplex für die Verwendung in der Softwareentwicklung. Daher ist die Assemblersprache auf niedriger Ebene für eine bestimmte Prozessorfamilie konzipiert, die verschiedene Anweisungen in symbolischem Code und in einer verständlicheren Form darstellt.
Vorteile der Assemblersprache
Ein Verständnis der Assemblersprache macht einen bewusst -
- Wie Programme mit Betriebssystem, Prozessor und BIOS kommunizieren;
- Wie Daten im Speicher und anderen externen Geräten dargestellt werden;
- Wie der Prozessor auf Anweisungen zugreift und diese ausführt;
- Wie Anweisungen auf Daten zugreifen und diese verarbeiten;
- Wie ein Programm auf externe Geräte zugreift.
Weitere Vorteile der Verwendung der Assemblersprache sind:
Es erfordert weniger Speicher und Ausführungszeit;
Es ermöglicht auf einfachere Weise hardwarespezifische komplexe Jobs.
Es ist für zeitkritische Jobs geeignet;
Es eignet sich am besten zum Schreiben von Interrupt-Serviceroutinen und anderen speicherresidenten Programmen.
Grundfunktionen der PC-Hardware
Die interne Haupthardware eines PCs besteht aus Prozessor, Speicher und Registern. Register sind Prozessorkomponenten, die Daten und Adressen enthalten. Um ein Programm auszuführen, kopiert das System es vom externen Gerät in den internen Speicher. Der Prozessor führt die Programmanweisungen aus.
Die grundlegende Einheit der Computerspeicherung ist ein bisschen; Es kann EIN (1) oder AUS (0) sein, und eine Gruppe von 8 verwandten Bits bildet auf den meisten modernen Computern ein Byte.
Das Paritätsbit wird also verwendet, um die Anzahl der Bits in einem Byte ungerade zu machen. Wenn die Parität gerade ist, geht das System davon aus, dass ein Paritätsfehler aufgetreten ist (obwohl selten), der möglicherweise auf einen Hardwarefehler oder eine elektrische Störung zurückzuführen ist.
Der Prozessor unterstützt die folgenden Datengrößen:
- Wort: Ein 2-Byte-Datenelement
- Doppelwort: Ein 4-Byte-Datenelement (32 Bit)
- Quadword: Ein 8-Byte-Datenelement (64 Bit)
- Absatz: Ein 16-Byte-Bereich (128 Bit)
- Kilobyte: 1024 Bytes
- Megabyte: 1.048.576 Bytes
Binärzahlensystem
Jedes Zahlensystem verwendet die Positionsnotation, dh jede Position, in die eine Ziffer geschrieben wird, hat einen anderen Positionswert. Jede Position ist die Potenz der Basis, die für das Binärzahlensystem 2 ist, und diese Potenzen beginnen bei 0 und erhöhen sich um 1.
Die folgende Tabelle zeigt die Positionswerte für eine 8-Bit-Binärzahl, bei der alle Bits auf ON gesetzt sind.
Bitwert | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
Positionieren Sie den Wert als Potenz der Basis 2 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
Bitnummer | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Der Wert einer Binärzahl basiert auf dem Vorhandensein von 1 Bits und ihrem Positionswert. Der Wert einer bestimmten Binärzahl ist also -
1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 = 255
das ist das gleiche wie 2 8 - 1.
Hexadezimalzahlensystem
Das Hexadezimalzahlensystem verwendet die Basis 16. Die Ziffern in diesem System reichen von 0 bis 15. Konventionell werden die Buchstaben A bis F verwendet, um die Hexadezimalziffern darzustellen, die den Dezimalwerten 10 bis 15 entsprechen.
Hexadezimalzahlen beim Rechnen werden zum Abkürzen langer binärer Darstellungen verwendet. Grundsätzlich stellt das Hexadezimalzahlensystem binäre Daten dar, indem jedes Byte in zwei Hälften geteilt und der Wert jedes halben Bytes ausgedrückt wird. Die folgende Tabelle enthält die Dezimal-, Binär- und Hexadezimaläquivalente.
Dezimalzahl | Binäre Darstellung | Hexadezimale Darstellung |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
2 | 10 | 2 |
3 | 11 | 3 |
4 | 100 | 4 |
5 | 101 | 5 |
6 | 110 | 6 |
7 | 111 | 7 |
8 | 1000 | 8 |
9 | 1001 | 9 |
10 | 1010 | EIN |
11 | 1011 | B. |
12 | 1100 | C. |
13 | 1101 | D. |
14 | 1110 | E. |
15 | 1111 | F. |
Um eine Binärzahl in ihre hexadezimale Entsprechung umzuwandeln, teilen Sie sie von rechts beginnend in Gruppen von jeweils 4 aufeinanderfolgenden Gruppen auf und schreiben Sie diese Gruppen über die entsprechenden Ziffern der hexadezimalen Zahl.
Example - Die Binärzahl 1000 1100 1101 0001 entspricht hexadezimal - 8CD1
Um eine Hexadezimalzahl in eine Binärzahl umzuwandeln, schreiben Sie einfach jede Hexadezimalzahl in ihr 4-stelliges Binäräquivalent.
Example - Die Hexadezimalzahl FAD8 entspricht der Binärzahl - 1111 1010 1101 1000
Binäre Arithmetik
Die folgende Tabelle zeigt vier einfache Regeln für die binäre Addition:
(ich) | (ii) | (iii) | (iv) |
---|---|---|---|
1 | |||
0 | 1 | 1 | 1 |
+0 | +0 | +1 | +1 |
= 0 | = 1 | = 10 | = 11 |
Die Regeln (iii) und (iv) zeigen einen Übertrag eines 1-Bits in die nächste linke Position.
Example
Dezimal | Binär |
---|---|
60 | 00111100 |
+42 | 00101010 |
102 | 01100110 |
Ein negativer Binärwert wird in ausgedrückt two's complement notation. Nach dieser Regel bedeutet das Konvertieren einer Binärzahl in ihren negativen Wert das Umkehren ihrer Bitwerte und das Addieren von 1 .
Example
Nummer 53 | 00110101 |
Bits umkehren | 11001010 |
Addiere 1 | 0000000 1 |
Nummer -53 | 11001011 |
Um einen Wert von einem anderen zu subtrahieren, konvertieren Sie die zu subtrahierende Zahl in das Zweierkomplementformat und addieren Sie die Zahlen .
Example
Subtrahiere 42 von 53
Nummer 53 | 00110101 |
Nummer 42 | 00101010 |
Kehren Sie die Bits von 42 um | 11010101 |
Addiere 1 | 0000000 1 |
Nummer -42 | 11010110 |
53 - 42 = 11 | 00001011 |
Der Überlauf der letzten 1 Bit geht verloren.
Adressierung von Daten im Speicher
Der Prozess, durch den der Prozessor die Ausführung von Anweisungen steuert, wird als bezeichnet fetch-decode-execute cycle oder der execution cycle. Es besteht aus drei kontinuierlichen Schritten -
- Abrufen der Anweisung aus dem Speicher
- Dekodieren oder Identifizieren der Anweisung
- Anweisung ausführen
Der Prozessor kann gleichzeitig auf ein oder mehrere Speicherbytes zugreifen. Betrachten wir eine Hexadezimalzahl 0725H. Diese Nummer benötigt zwei Bytes Speicher. Das Byte höherer Ordnung oder das höchstwertige Byte ist 07 und das Byte niedriger Ordnung ist 25.
Der Prozessor speichert Daten in umgekehrter Bytesequenz, dh ein Byte niedriger Ordnung wird in einer Adresse mit niedrigem Speicher und ein Byte höherer Ordnung in einer Adresse mit hohem Speicher gespeichert. Wenn der Prozessor also den Wert 0725H vom Register in den Speicher bringt, überträgt er 25 zuerst an die niedrigere Speicheradresse und 07 an die nächste Speicheradresse.
x: Speicheradresse
Wenn der Prozessor die numerischen Daten aus dem Speicher zum Registrieren erhält, kehrt er die Bytes erneut um. Es gibt zwei Arten von Speicheradressen -
Absolute Adresse - eine direkte Referenz eines bestimmten Ortes.
Segmentadresse (oder Offset) - Startadresse eines Speichersegments mit dem Offsetwert.
Einrichtung der lokalen Umgebung
Die Assemblersprache hängt vom Befehlssatz und der Architektur des Prozessors ab. In diesem Tutorial konzentrieren wir uns auf Intel-32-Prozessoren wie Pentium. Um diesem Tutorial zu folgen, benötigen Sie -
- Ein IBM PC oder ein gleichwertiger kompatibler Computer
- Eine Kopie des Linux-Betriebssystems
- Eine Kopie des NASM-Assembler-Programms
Es gibt viele gute Assembler-Programme wie -
- Microsoft Assembler (MASM)
- Borland Turbo Assembler (TASM)
- Der GNU Assembler (GAS)
Wir werden den NASM-Assembler so verwenden, wie er ist -
- Kostenlos. Sie können es aus verschiedenen Webquellen herunterladen.
- Gut dokumentiert und Sie erhalten viele Informationen im Internet.
- Kann sowohl unter Linux als auch unter Windows verwendet werden.
NASM installieren
Wenn Sie während der Installation von Linux "Entwicklungstools" auswählen, wird NASM möglicherweise zusammen mit dem Linux-Betriebssystem installiert, und Sie müssen es nicht separat herunterladen und installieren. Führen Sie die folgenden Schritte aus, um zu überprüfen, ob NASM bereits installiert ist:
Öffnen Sie ein Linux-Terminal.
Art whereis nasm und drücken Sie ENTER.
Wenn es bereits installiert ist, wird eine Zeile wie nasm: / usr / bin / nasm angezeigt. Andernfalls sehen Sie nur nasm : , dann müssen Sie NASM installieren.
Führen Sie die folgenden Schritte aus, um NASM zu installieren:
Überprüfen Sie die Netwide Assembler (NASM) Website für die neueste Version verfügbar .
Laden Sie das Linux- Quellarchiv herunter
nasm-X.XX.ta.gz
, in demX.XX
sich die NASM-Versionsnummer im Archiv befindet.Entpacken Sie das Archiv in ein Verzeichnis, das ein Unterverzeichnis erstellt
nasm-X. XX
.CD an
nasm-X.XX
und tippen./configure. Dieses Shell-Skript findet den besten C-Compiler, um Makefiles entsprechend zu verwenden und einzurichten.Art make um die Nasm- und Ndisasm-Binärdateien zu erstellen.
Art make install um nasm und ndisasm in / usr / local / bin zu installieren und die manpages zu installieren.
Dies sollte NASM auf Ihrem System installieren. Alternativ können Sie eine RPM-Distribution für Fedora Linux verwenden. Diese Version ist einfacher zu installieren. Doppelklicken Sie einfach auf die RPM-Datei.
Ein Montageprogramm kann in drei Abschnitte unterteilt werden -
Das data Sektion,
Das bss Abschnitt und
Das text Sektion.
Die Daten Abschnitt
Das dataAbschnitt wird zum Deklarieren initialisierter Daten oder Konstanten verwendet. Diese Daten ändern sich zur Laufzeit nicht. In diesem Abschnitt können Sie verschiedene konstante Werte, Dateinamen oder Puffergrößen usw. deklarieren.
Die Syntax zum Deklarieren des Datenabschnitts lautet -
section.data
Die bss Sektion
Das bssAbschnitt wird zum Deklarieren von Variablen verwendet. Die Syntax zum Deklarieren des bss-Abschnitts lautet -
section.bss
Der Textabschnitt
Das textAbschnitt wird verwendet, um den tatsächlichen Code zu behalten. Dieser Abschnitt muss mit der Erklärung beginnenglobal _start, der dem Kernel mitteilt, wo die Programmausführung beginnt.
Die Syntax zum Deklarieren des Textabschnitts lautet -
section.text
global _start
_start:
Bemerkungen
Der Kommentar zur Assemblersprache beginnt mit einem Semikolon (;). Es kann jedes druckbare Zeichen enthalten, einschließlich Leerzeichen. Es kann in einer eigenen Zeile erscheinen, wie -
; This program displays a message on screen
oder in derselben Zeile zusammen mit einer Anweisung wie -
add eax, ebx ; adds ebx to eax
Assembler-Anweisungen
Assembler-Programme bestehen aus drei Arten von Anweisungen:
- Ausführbare Anweisungen oder Anweisungen,
- Assembler-Direktiven oder Pseudo-Ops und
- Macros.
Das executable instructions oder einfach instructionsSagen Sie dem Prozessor, was zu tun ist. Jede Anweisung besteht aus einemoperation code(Opcode). Jeder ausführbare Befehl erzeugt einen maschinensprachlichen Befehl.
Das assembler directives oder pseudo-opsInformieren Sie den Assembler über die verschiedenen Aspekte des Assemblierungsprozesses. Diese sind nicht ausführbar und generieren keine Anweisungen in Maschinensprache.
Macros sind im Grunde ein Textsubstitutionsmechanismus.
Syntax von Assembler-Anweisungen
Assembler-Anweisungen werden mit einer Anweisung pro Zeile eingegeben. Jede Anweisung folgt dem folgenden Format:
[label] mnemonic [operands] [;comment]
Die Felder in eckigen Klammern sind optional. Ein Basisbefehl besteht aus zwei Teilen, der erste ist der Name des Befehls (oder der Mnemonik), der ausgeführt werden soll, und der zweite sind die Operanden oder die Parameter des Befehls.
Im Folgenden finden Sie einige Beispiele für typische Anweisungen in Assemblersprache:
INC COUNT ; Increment the memory variable COUNT
MOV TOTAL, 48 ; Transfer the value 48 in the
; memory variable TOTAL
ADD AH, BH ; Add the content of the
; BH register into the AH register
AND MASK1, 128 ; Perform AND operation on the
; variable MASK1 and 128
ADD MARKS, 10 ; Add 10 to the variable MARKS
MOV AL, 10 ; Transfer the value 10 to the AL register
Das Hello World-Programm in der Versammlung
Der folgende Assembler-Code zeigt die Zeichenfolge 'Hello World' auf dem Bildschirm an -
section .text
global _start ;must be declared for linker (ld)
_start: ;tells linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Hello, world!', 0xa ;string to be printed
len equ $ - msg ;length of the string
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
Hello, world!
Kompilieren und Verknüpfen eines Assembly-Programms in NASM
Stellen Sie sicher, dass Sie den Pfad von festgelegt haben nasm und ldBinärdateien in Ihrer Umgebungsvariablen PATH. Führen Sie nun die folgenden Schritte aus, um das obige Programm zu kompilieren und zu verknüpfen:
Geben Sie den obigen Code mit einem Texteditor ein und speichern Sie ihn als hello.asm.
Stellen Sie sicher, dass Sie sich in demselben Verzeichnis befinden, in dem Sie gespeichert haben hello.asm.
Geben Sie ein, um das Programm zusammenzustellen nasm -f elf hello.asm
Wenn ein Fehler auftritt, werden Sie zu diesem Zeitpunkt dazu aufgefordert. Andernfalls wird eine Objektdatei Ihres Programms benannthello.o wird erstellt.
Geben Sie ein, um die Objektdatei zu verknüpfen und eine ausführbare Datei mit dem Namen "Hallo" zu erstellen ld -m elf_i386 -s -o hello hello.o
Führen Sie das Programm durch Eingabe aus ./hello
Wenn Sie alles richtig gemacht haben, wird "Hallo Welt!" Angezeigt. auf dem Bildschirm.
Wir haben bereits die drei Abschnitte eines Montageprogramms besprochen. Diese Abschnitte repräsentieren auch verschiedene Speichersegmente.
Interessanterweise erhalten Sie das gleiche Ergebnis, wenn Sie das Schlüsselwort section durch segment ersetzen. Versuchen Sie den folgenden Code -
segment .text ;code segment
global _start ;must be declared for linker
_start: ;tell linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
segment .data ;data segment
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
Hello, world!
Speichersegmente
Ein segmentiertes Speichermodell unterteilt den Systemspeicher in Gruppen unabhängiger Segmente, auf die durch Zeiger in den Segmentregistern verwiesen wird. Jedes Segment wird verwendet, um einen bestimmten Datentyp zu enthalten. Ein Segment enthält Befehlscodes, ein anderes Segment speichert die Datenelemente und ein drittes Segment enthält den Programmstapel.
In Anbetracht der obigen Diskussion können wir verschiedene Speichersegmente als - spezifizieren
Data segment - Es wird vertreten durch .data Abschnitt und die .bss. Der Abschnitt .data wird verwendet, um den Speicherbereich zu deklarieren, in dem Datenelemente für das Programm gespeichert sind. Dieser Abschnitt kann nach der Deklaration der Datenelemente nicht erweitert werden und bleibt im gesamten Programm statisch.
Der Abschnitt .bss ist auch ein statischer Speicherabschnitt, der Puffer für Daten enthält, die später im Programm deklariert werden sollen. Dieser Pufferspeicher ist mit Null gefüllt.
Code segment - Es wird vertreten durch .textSektion. Dies definiert einen Bereich im Speicher, in dem die Befehlscodes gespeichert sind. Dies ist auch ein fester Bereich.
Stack - Dieses Segment enthält Datenwerte, die an Funktionen und Prozeduren innerhalb des Programms übergeben werden.
Prozessoroperationen umfassen meistens die Verarbeitung von Daten. Diese Daten können im Speicher gespeichert und von dort aus abgerufen werden. Das Lesen von Daten aus dem Speicher und das Speichern von Daten im Speicher verlangsamt jedoch den Prozessor, da komplizierte Prozesse zum Senden der Datenanforderung über den Steuerbus und in die Speichereinheit und zum Abrufen der Daten über denselben Kanal erforderlich sind.
Um die Prozessoroperationen zu beschleunigen, enthält der Prozessor einige interne Speicherstellen, die als aufgerufen bezeichnet werden registers.
Die Register speichern Datenelemente zur Verarbeitung, ohne auf den Speicher zugreifen zu müssen. Eine begrenzte Anzahl von Registern ist in den Prozessorchip eingebaut.
Prozessorregister
In der IA-32-Architektur gibt es zehn 32-Bit- und sechs 16-Bit-Prozessorregister. Die Register sind in drei Kategorien unterteilt -
- Allgemeine Register,
- Steuerregister und
- Segmentregister.
Die allgemeinen Register sind weiter in die folgenden Gruppen unterteilt:
- Datenregister,
- Zeigerregister und
- Indexregister.
Datenregister
Vier 32-Bit-Datenregister werden für arithmetische, logische und andere Operationen verwendet. Diese 32-Bit-Register können auf drei Arten verwendet werden:
Als vollständige 32-Bit-Datenregister: EAX, EBX, ECX, EDX.
Die unteren Hälften der 32-Bit-Register können als vier 16-Bit-Datenregister verwendet werden: AX, BX, CX und DX.
Die untere und die obere Hälfte der oben genannten vier 16-Bit-Register können als acht 8-Bit-Datenregister verwendet werden: AH, AL, BH, BL, CH, CL, DH und DL.
Einige dieser Datenregister werden speziell für arithmetische Operationen verwendet.
AX is the primary accumulator;; Es wird in Eingabe- / Ausgabe- und den meisten arithmetischen Anweisungen verwendet. Beispielsweise wird bei einer Multiplikationsoperation ein Operand entsprechend der Größe des Operanden im EAX- oder AX- oder AL-Register gespeichert.
BX is known as the base register, wie es bei der indizierten Adressierung verwendet werden könnte.
CX is known as the count registerAls ECX speichern CX-Register die Schleifenzahl in iterativen Operationen.
DX is known as the data register. Es wird auch bei Eingabe- / Ausgabeoperationen verwendet. Es wird auch mit dem AX-Register zusammen mit DX zum Multiplizieren und Teilen von Operationen mit großen Werten verwendet.
Zeigerregister
Die Zeigerregister sind 32-Bit-EIP-, ESP- und EBP-Register und entsprechende 16-Bit-Rechtsabschnitte IP, SP und BP. Es gibt drei Kategorien von Zeigerregistern -
Instruction Pointer (IP)- Das 16-Bit-IP-Register speichert die Offset-Adresse des nächsten auszuführenden Befehls. IP in Verbindung mit dem CS-Register (als CS: IP) gibt die vollständige Adresse des aktuellen Befehls im Codesegment an.
Stack Pointer (SP)- Das 16-Bit-SP-Register liefert den Versatzwert innerhalb des Programmstapels. SP in Verbindung mit dem SS-Register (SS: SP) bezieht sich auf die aktuelle Position von Daten oder Adressen innerhalb des Programmstapels.
Base Pointer (BP)- Das 16-Bit-BP-Register hilft hauptsächlich bei der Referenzierung der Parametervariablen, die an ein Unterprogramm übergeben werden. Die Adresse im SS-Register wird mit dem Offset in BP kombiniert, um die Position des Parameters zu erhalten. BP kann auch mit DI und SI als Basisregister für die spezielle Adressierung kombiniert werden.
Indexregister
Die 32-Bit-Indexregister ESI und EDI sowie ihre 16-Bit-Teile ganz rechts. SI und DI werden für die indizierte Adressierung verwendet und manchmal zusätzlich und subtrahiert. Es gibt zwei Sätze von Indexzeigern -
Source Index (SI) - Es wird als Quellindex für Zeichenfolgenoperationen verwendet.
Destination Index (DI) - Es wird als Zielindex für Zeichenfolgenoperationen verwendet.
Kontrollregister
Das 32-Bit-Befehlszeigerregister und das 32-Bit-Flagsregister zusammen werden als Steuerregister betrachtet.
Viele Anweisungen umfassen Vergleiche und mathematische Berechnungen und ändern den Status der Flags. Einige andere bedingte Anweisungen testen den Wert dieser Statusflags, um den Kontrollfluss an einen anderen Ort zu leiten.
Die gemeinsamen Flag-Bits sind:
Overflow Flag (OF) - Zeigt den Überlauf eines höherwertigen Bits (Bit ganz links) nach einer vorzeichenbehafteten arithmetischen Operation an.
Direction Flag (DF)- Er bestimmt die linke oder rechte Richtung zum Verschieben oder Vergleichen von Zeichenfolgendaten. Wenn der DF-Wert 0 ist, wird die Zeichenfolgenoperation von links nach rechts ausgeführt, und wenn der Wert auf 1 gesetzt wird, wird die Zeichenfolgenoperation von rechts nach links ausgeführt.
Interrupt Flag (IF)- Hiermit wird festgelegt, ob externe Interrupts wie Tastatureingaben usw. ignoriert oder verarbeitet werden sollen. Es deaktiviert den externen Interrupt, wenn der Wert 0 ist, und aktiviert Interrupts, wenn es auf 1 gesetzt ist.
Trap Flag (TF)- Hiermit können Sie den Betrieb des Prozessors im Einzelschrittmodus einstellen. Das von uns verwendete DEBUG-Programm setzt das Trap-Flag, sodass wir die Ausführung einzeln ausführen können.
Sign Flag (SF)- Es zeigt das Vorzeichen des Ergebnisses einer arithmetischen Operation. Dieses Flag wird gemäß dem Vorzeichen eines Datenelements nach der arithmetischen Operation gesetzt. Das Vorzeichen wird durch die höhere Ordnung des Bit ganz links angezeigt. Ein positives Ergebnis löscht den Wert von SF auf 0 und ein negatives Ergebnis setzt ihn auf 1.
Zero Flag (ZF)- Zeigt das Ergebnis einer Arithmetik- oder Vergleichsoperation an. Ein Ergebnis ungleich Null löscht das Null-Flag auf 0 und ein Null-Ergebnis setzt es auf 1.
Auxiliary Carry Flag (AF)- Es enthält den Übertrag von Bit 3 nach Bit 4 nach einer arithmetischen Operation; wird für spezialisierte Arithmetik verwendet. Der AF wird gesetzt, wenn eine 1-Byte-Arithmetikoperation einen Übertrag von Bit 3 nach Bit 4 verursacht.
Parity Flag (PF)- Zeigt die Gesamtzahl der 1-Bits im Ergebnis einer arithmetischen Operation an. Eine gerade Anzahl von 1 Bits löscht das Paritätsflag auf 0 und eine ungerade Anzahl von 1 Bits setzt das Paritätsflag auf 1.
Carry Flag (CF)- Es enthält den Übertrag von 0 oder 1 von einem höherwertigen Bit (ganz links) nach einer arithmetischen Operation. Es speichert auch den Inhalt des letzten Bits einer Verschiebungs- oder Drehoperation .
Die folgende Tabelle gibt die Position der Flag-Bits im 16-Bit-Flags-Register an:
Flagge: | Ö | D. | ich | T. | S. | Z. | EIN | P. | C. | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit Nr.: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Segmentregister
Segmente sind bestimmte Bereiche, die in einem Programm zum Enthalten von Daten, Code und Stapel definiert sind. Es gibt drei Hauptsegmente -
Code Segment- Es enthält alle auszuführenden Anweisungen. Ein 16-Bit-Codesegmentregister oder CS-Register speichert die Startadresse des Codesegments.
Data Segment- Es enthält Daten, Konstanten und Arbeitsbereiche. Ein 16-Bit-Datensegmentregister oder DS-Register speichert die Startadresse des Datensegments.
Stack Segment- Es enthält Daten und Rücksprungadressen von Prozeduren oder Unterprogrammen. Es ist als 'Stack'-Datenstruktur implementiert. Das Stapelsegmentregister oder SS-Register speichert die Startadresse des Stapels.
Neben den DS-, CS- und SS-Registern gibt es weitere zusätzliche Segmentregister - ES (zusätzliches Segment), FS und GS, die zusätzliche Segmente zum Speichern von Daten bereitstellen.
Bei der Baugruppenprogrammierung muss ein Programm auf die Speicherorte zugreifen. Alle Speicherplätze innerhalb eines Segments sind relativ zur Startadresse des Segments. Ein Segment beginnt in einer Adresse, die gleichmäßig durch 16 oder hexadezimal 10 teilbar ist. Die hexadezimale Ziffer ganz rechts in all diesen Speicheradressen ist also 0, was im Allgemeinen nicht in den Segmentregistern gespeichert ist.
In den Segmentregistern werden die Startadressen eines Segments gespeichert. Um die genaue Position von Daten oder Anweisungen innerhalb eines Segments zu erhalten, ist ein Versatzwert (oder eine Verschiebung) erforderlich. Um auf einen Speicherplatz in einem Segment zu verweisen, kombiniert der Prozessor die Segmentadresse im Segmentregister mit dem Versatzwert des Ortes.
Beispiel
Schauen Sie sich das folgende einfache Programm an, um die Verwendung von Registern in der Assembly-Programmierung zu verstehen. Dieses Programm zeigt 9 Sterne auf dem Bildschirm zusammen mit einer einfachen Meldung an -
section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,9 ;message length
mov ecx,s2 ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Displaying 9 stars',0xa ;a message
len equ $ - msg ;length of message
s2 times 9 db '*'
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
Displaying 9 stars
*********
Systemaufrufe sind APIs für die Schnittstelle zwischen dem Benutzerbereich und dem Kernelbereich. Wir haben die Systemaufrufe bereits verwendet. sys_write und sys_exit zum Schreiben in den Bildschirm bzw. zum Verlassen des Programms.
Linux-Systemaufrufe
Sie können Linux-Systemaufrufe in Ihren Assembly-Programmen verwenden. Sie müssen die folgenden Schritte ausführen, um Linux-Systemaufrufe in Ihrem Programm zu verwenden:
- Tragen Sie die Systemrufnummer in das EAX-Register ein.
- Speichern Sie die Argumente für den Systemaufruf in den Registern EBX, ECX usw.
- Rufen Sie den entsprechenden Interrupt an (80h).
- Das Ergebnis wird normalerweise im EAX-Register zurückgegeben.
Es gibt sechs Register, in denen die Argumente des verwendeten Systemaufrufs gespeichert sind. Dies sind EBX, ECX, EDX, ESI, EDI und EBP. Diese Register verwenden die aufeinander folgenden Argumente, beginnend mit dem EBX-Register. Wenn mehr als sechs Argumente vorhanden sind, wird der Speicherort des ersten Arguments im EBX-Register gespeichert.
Das folgende Codefragment zeigt die Verwendung des Systemaufrufs sys_exit -
mov eax,1 ; system call number (sys_exit)
int 0x80 ; call kernel
Das folgende Codefragment zeigt die Verwendung des Systemaufrufs sys_write -
mov edx,4 ; message length
mov ecx,msg ; message to write
mov ebx,1 ; file descriptor (stdout)
mov eax,4 ; system call number (sys_write)
int 0x80 ; call kernel
Alle Systemaufrufe werden in /usr/include/asm/unistd.h zusammen mit ihren Nummern aufgelistet (der Wert, der in EAX eingegeben werden muss , bevor Sie int 80h aufrufen).
Die folgende Tabelle zeigt einige der in diesem Lernprogramm verwendeten Systemaufrufe -
% eax | Name | % ebx | % ecx | % edx | % esx | % edi |
---|---|---|---|---|---|---|
1 | sys_exit | int | - - | - - | - - | - - |
2 | sys_fork | struct pt_regs | - - | - - | - - | - - |
3 | sys_read | unsigned int | char * | size_t | - - | - - |
4 | sys_write | unsigned int | const char * | size_t | - - | - - |
5 | sys_open | const char * | int | int | - - | - - |
6 | sys_close | unsigned int | - - | - - | - - | - - |
Beispiel
Im folgenden Beispiel wird eine Nummer von der Tastatur gelesen und auf dem Bildschirm angezeigt.
section .data ;Data segment
userMsg db 'Please enter a number: ' ;Ask the user to enter a number
lenUserMsg equ $-userMsg ;The length of the message
dispMsg db 'You have entered: '
lenDispMsg equ $-dispMsg
section .bss ;Uninitialized data
num resb 5
section .text ;Code Segment
global _start
_start: ;User prompt
mov eax, 4
mov ebx, 1
mov ecx, userMsg
mov edx, lenUserMsg
int 80h
;Read and store the user input
mov eax, 3
mov ebx, 2
mov ecx, num
mov edx, 5 ;5 bytes (numeric, 1 for sign) of that information
int 80h
;Output the message 'The entered number is: '
mov eax, 4
mov ebx, 1
mov ecx, dispMsg
mov edx, lenDispMsg
int 80h
;Output the number entered
mov eax, 4
mov ebx, 1
mov ecx, num
mov edx, 5
int 80h
; Exit code
mov eax, 1
mov ebx, 0
int 80h
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
Please enter a number:
1234
You have entered:1234
Die meisten Assembler-Anweisungen erfordern die Verarbeitung von Operanden. Eine Operandenadresse gibt den Ort an, an dem die zu verarbeitenden Daten gespeichert werden. Einige Anweisungen erfordern keinen Operanden, während andere Anweisungen einen, zwei oder drei Operanden erfordern können.
Wenn ein Befehl zwei Operanden erfordert, ist der erste Operand im Allgemeinen das Ziel, das Daten in einem Register oder Speicherplatz enthält, und der zweite Operand ist die Quelle. Die Quelle enthält entweder die zu liefernden Daten (sofortige Adressierung) oder die Adresse (im Register oder Speicher) der Daten. Im Allgemeinen bleiben die Quelldaten nach der Operation unverändert.
Die drei grundlegenden Adressierungsmodi sind:
- Adressierung registrieren
- Sofortige Adressierung
- Speicheradressierung
Adressierung registrieren
In diesem Adressierungsmodus enthält ein Register den Operanden. Abhängig von der Anweisung kann das Register der erste Operand, der zweite Operand oder beides sein.
Zum Beispiel,
MOV DX, TAX_RATE ; Register in first operand
MOV COUNT, CX ; Register in second operand
MOV EAX, EBX ; Both the operands are in registers
Da die Verarbeitung von Daten zwischen Registern keinen Speicher erfordert, bietet sie die schnellste Verarbeitung von Daten.
Sofortige Adressierung
Ein unmittelbarer Operand hat einen konstanten Wert oder einen Ausdruck. Wenn ein Befehl mit zwei Operanden eine sofortige Adressierung verwendet, kann der erste Operand ein Register oder ein Speicherort sein, und der zweite Operand ist eine unmittelbare Konstante. Der erste Operand definiert die Länge der Daten.
Zum Beispiel,
BYTE_VALUE DB 150 ; A byte value is defined
WORD_VALUE DW 300 ; A word value is defined
ADD BYTE_VALUE, 65 ; An immediate operand 65 is added
MOV AX, 45H ; Immediate constant 45H is transferred to AX
Direkte Speicheradressierung
Wenn Operanden im Speicheradressierungsmodus angegeben werden, ist ein direkter Zugriff auf den Hauptspeicher, normalerweise auf das Datensegment, erforderlich. Diese Art der Adressierung führt zu einer langsameren Datenverarbeitung. Um den genauen Speicherort der Daten im Speicher zu ermitteln, benötigen wir die Segmentstartadresse, die normalerweise im DS-Register enthalten ist, und einen Versatzwert. Dieser Offsetwert wird auch genannteffective address.
Im direkten Adressierungsmodus wird der Versatzwert direkt als Teil des Befehls angegeben, der normalerweise durch den Variablennamen angezeigt wird. Der Assembler berechnet den Versatzwert und verwaltet eine Symboltabelle, in der die Versatzwerte aller im Programm verwendeten Variablen gespeichert sind.
Bei der direkten Speicheradressierung bezieht sich einer der Operanden auf einen Speicherort und der andere Operand auf ein Register.
Zum Beispiel,
ADD BYTE_VALUE, DL ; Adds the register in the memory location
MOV BX, WORD_VALUE ; Operand from the memory is added to register
Direct-Offset-Adressierung
Dieser Adressierungsmodus verwendet die arithmetischen Operatoren, um eine Adresse zu ändern. Schauen Sie sich beispielsweise die folgenden Definitionen an, die Datentabellen definieren:
BYTE_TABLE DB 14, 15, 22, 45 ; Tables of bytes
WORD_TABLE DW 134, 345, 564, 123 ; Tables of words
Die folgenden Operationen greifen auf Daten aus den Tabellen im Speicher in Register zu -
MOV CL, BYTE_TABLE[2] ; Gets the 3rd element of the BYTE_TABLE
MOV CL, BYTE_TABLE + 2 ; Gets the 3rd element of the BYTE_TABLE
MOV CX, WORD_TABLE[3] ; Gets the 4th element of the WORD_TABLE
MOV CX, WORD_TABLE + 3 ; Gets the 4th element of the WORD_TABLE
Indirekte Speicheradressierung
Dieser Adressierungsmodus nutzt die Fähigkeit des Computers zur Segmentierung: Versatzadressierung . Im Allgemeinen werden zu diesem Zweck die Basisregister EBX, EBP (oder BX, BP) und die Indexregister (DI, SI) verwendet, die in eckigen Klammern für Speicherreferenzen codiert sind.
Die indirekte Adressierung wird im Allgemeinen für Variablen verwendet, die mehrere Elemente wie Arrays enthalten. Die Startadresse des Arrays wird beispielsweise im EBX-Register gespeichert.
Das folgende Codefragment zeigt, wie auf verschiedene Elemente der Variablen zugegriffen wird.
MY_TABLE TIMES 10 DW 0 ; Allocates 10 words (2 bytes) each initialized to 0
MOV EBX, [MY_TABLE] ; Effective Address of MY_TABLE in EBX
MOV [EBX], 110 ; MY_TABLE[0] = 110
ADD EBX, 2 ; EBX = EBX +2
MOV [EBX], 123 ; MY_TABLE[1] = 123
Die MOV-Anweisung
Wir haben bereits den MOV-Befehl verwendet, der zum Verschieben von Daten von einem Speicherplatz in einen anderen verwendet wird. Der MOV-Befehl benötigt zwei Operanden.
Syntax
Die Syntax des MOV-Befehls lautet -
MOV destination, source
Der MOV-Befehl kann eine der folgenden fünf Formen haben:
MOV register, register
MOV register, immediate
MOV memory, immediate
MOV register, memory
MOV memory, register
Bitte beachten Sie, dass -
- Beide Operanden im MOV-Betrieb sollten gleich groß sein
- Der Wert des Quelloperanden bleibt unverändert
Der MOV-Befehl verursacht manchmal Mehrdeutigkeiten. Schauen Sie sich zum Beispiel die Aussagen an -
MOV EBX, [MY_TABLE] ; Effective Address of MY_TABLE in EBX
MOV [EBX], 110 ; MY_TABLE[0] = 110
Es ist nicht klar, ob Sie ein Byte-Äquivalent oder ein Wort-Äquivalent der Zahl 110 verschieben möchten. In solchen Fällen ist es ratsam, a zu verwenden type specifier.
Die folgende Tabelle zeigt einige der gängigen Typspezifizierer -
Typspezifizierer | Bytes adressiert |
---|---|
BYTE | 1 |
WORT | 2 |
DWORD | 4 |
QWORT | 8 |
TBYTE | 10 |
Beispiel
Das folgende Programm veranschaulicht einige der oben diskutierten Konzepte. Es speichert einen Namen 'Zara Ali' im Datenbereich des Speichers, ändert dann programmgesteuert seinen Wert in einen anderen Namen 'Nuha Ali' und zeigt beide Namen an.
section .text
global _start ;must be declared for linker (ld)
_start: ;tell linker entry point
;writing the name 'Zara Ali'
mov edx,9 ;message length
mov ecx, name ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov [name], dword 'Nuha' ; Changed the name to Nuha Ali
;writing the name 'Nuha Ali'
mov edx,8 ;message length
mov ecx,name ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
name db 'Zara Ali '
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
Zara Ali Nuha Ali
NASM bietet verschiedene define directiveszum Reservieren von Speicherplatz für Variablen. Die Direktive define Assembler wird für die Zuweisung von Speicherplatz verwendet. Es kann verwendet werden, um ein oder mehrere Bytes zu reservieren und zu initialisieren.
Zuweisen von Speicherplatz für initialisierte Daten
Die Syntax für die Speicherzuweisungsanweisung für initialisierte Daten lautet -
[variable-name] define-directive initial-value [,initial-value]...
Wobei Variablenname die Kennung für jeden Speicherplatz ist. Der Assembler ordnet jedem im Datensegment definierten Variablennamen einen Versatzwert zu.
Es gibt fünf Grundformen der Definitionsrichtlinie:
Richtlinie | Zweck | Lagerraum |
---|---|---|
DB | Byte definieren | weist 1 Byte zu |
DW | Wort definieren | weist 2 Bytes zu |
DD | Doppelwort definieren | weist 4 Bytes zu |
DQ | Quadword definieren | weist 8 Bytes zu |
DT | Definieren Sie zehn Bytes | weist 10 Bytes zu |
Im Folgenden finden Sie einige Beispiele für die Verwendung von definierten Anweisungen:
choice DB 'y'
number DW 12345
neg_number DW -12345
big_number DQ 123456789
real_number1 DD 1.234
real_number2 DQ 123.456
Bitte beachten Sie, dass -
Jedes Zeichenbyte wird als ASCII-Wert hexadezimal gespeichert.
Jeder Dezimalwert wird automatisch in sein 16-Bit-Binäräquivalent konvertiert und als Hexadezimalzahl gespeichert.
Der Prozessor verwendet die Little-Endian-Bytereihenfolge.
Negative Zahlen werden in die Zweierkomplementdarstellung umgewandelt.
Kurze und lange Gleitkommazahlen werden mit 32 bzw. 64 Bit dargestellt.
Das folgende Programm zeigt die Verwendung der define-Direktive -
section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry point
mov edx,1 ;message length
mov ecx,choice ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
choice DB 'y'
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
y
Zuweisen von Speicherplatz für nicht initialisierte Daten
Die Reserveanweisungen werden verwendet, um Speicherplatz für nicht initialisierte Daten zu reservieren. Die Reserveanweisungen verwenden einen einzelnen Operanden, der die Anzahl der zu reservierenden Speichereinheiten angibt. Jede Definitionsrichtlinie hat eine zugehörige Reserverichtlinie.
Es gibt fünf Grundformen der Reserverichtlinie:
Richtlinie | Zweck |
---|---|
RESB | Reserviere ein Byte |
RESW | Reserviere ein Wort |
RESD | Reserviere ein Doppelwort |
RESQ | Reserviere ein Quadword |
SICH AUSRUHEN | Reservieren Sie zehn Bytes |
Mehrere Definitionen
Sie können mehrere Datendefinitionsanweisungen in einem Programm haben. Zum Beispiel -
choice DB 'Y' ;ASCII of y = 79H
number1 DW 12345 ;12345D = 3039H
number2 DD 12345679 ;123456789D = 75BCD15H
Der Assembler reserviert zusammenhängenden Speicher für mehrere Variablendefinitionen.
Mehrere Initialisierungen
Die TIMES-Direktive ermöglicht mehrere Initialisierungen auf denselben Wert. Beispielsweise kann ein Array mit dem Namen markierungen der Größe 9 mit der folgenden Anweisung definiert und auf Null initialisiert werden:
marks TIMES 9 DW 0
Die TIMES-Direktive ist nützlich beim Definieren von Arrays und Tabellen. Das folgende Programm zeigt 9 Sternchen auf dem Bildschirm an -
section .text
global _start ;must be declared for linker (ld)
_start: ;tell linker entry point
mov edx,9 ;message length
mov ecx, stars ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
stars times 9 db '*'
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
*********
Es gibt mehrere von NASM bereitgestellte Anweisungen, die Konstanten definieren. Wir haben die EQU-Richtlinie bereits in früheren Kapiteln verwendet. Wir werden insbesondere drei Richtlinien diskutieren -
- EQU
- %assign
- %define
Die EQU-Richtlinie
Das EQUDie Direktive wird zum Definieren von Konstanten verwendet. Die Syntax der EQU-Direktive lautet wie folgt:
CONSTANT_NAME EQU expression
Zum Beispiel,
TOTAL_STUDENTS equ 50
Sie können diesen konstanten Wert dann in Ihrem Code verwenden, wie z.
mov ecx, TOTAL_STUDENTS
cmp eax, TOTAL_STUDENTS
Der Operand einer EQU-Anweisung kann ein Ausdruck sein -
LENGTH equ 20
WIDTH equ 10
AREA equ length * width
Das obige Codesegment würde AREA als 200 definieren.
Beispiel
Das folgende Beispiel zeigt die Verwendung der EQU-Direktive -
SYS_EXIT equ 1
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80
mov eax,SYS_EXIT ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg1 db 'Hello, programmers!',0xA,0xD
len1 equ $ - msg1
msg2 db 'Welcome to the world of,', 0xA,0xD
len2 equ $ - msg2 msg3 db 'Linux assembly programming! ' len3 equ $- msg3
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
Die% weisen Richtlinie zu
Das %assignDirektive kann verwendet werden, um numerische Konstanten wie die EQU-Direktive zu definieren. Diese Richtlinie ermöglicht eine Neudefinition. Beispielsweise können Sie die Konstante TOTAL als - definieren.
%assign TOTAL 10
Später im Code können Sie ihn neu definieren als -
%assign TOTAL 20
Diese Richtlinie unterscheidet zwischen Groß- und Kleinschreibung.
Die% definieren Richtlinie
Das %defineDie Direktive ermöglicht das Definieren von numerischen und String-Konstanten. Diese Direktive ähnelt der #define in C. Beispielsweise können Sie die Konstante PTR als - definieren
%define PTR [EBP+4]
Der obige Code ersetzt PTR durch [EBP + 4].
Diese Richtlinie ermöglicht auch eine Neudefinition und unterscheidet zwischen Groß- und Kleinschreibung.
Die INC-Anweisung
Der INC-Befehl wird zum Inkrementieren eines Operanden um eins verwendet. Es funktioniert mit einem einzelnen Operanden, der sich entweder in einem Register oder im Speicher befinden kann.
Syntax
Der INC-Befehl hat die folgende Syntax:
INC destination
Der Operand Ziel könnte einen 8-Bit, 16-Bit oder 32-Bit - Operanden sein.
Beispiel
INC EBX ; Increments 32-bit register
INC DL ; Increments 8-bit register
INC [count] ; Increments the count variable
Die DEC-Anweisung
Der DEC-Befehl wird zum Dekrementieren eines Operanden um eins verwendet. Es funktioniert mit einem einzelnen Operanden, der sich entweder in einem Register oder im Speicher befinden kann.
Syntax
Der DEC-Befehl hat die folgende Syntax:
DEC destination
Der Operand Ziel könnte einen 8-Bit, 16-Bit oder 32-Bit - Operanden sein.
Beispiel
segment .data
count dw 0
value db 15
segment .text
inc [count]
dec [value]
mov ebx, count
inc word [ebx]
mov esi, value
dec byte [esi]
Die ADD- und SUB-Anweisungen
Die ADD- und SUB-Anweisungen werden zum Durchführen einer einfachen Addition / Subtraktion von Binärdaten in Byte-, Wort- und Doppelwortgröße verwendet, dh zum Addieren oder Subtrahieren von 8-Bit-, 16-Bit- bzw. 32-Bit-Operanden.
Syntax
Die Anweisungen ADD und SUB haben die folgende Syntax:
ADD/SUB destination, source
Der ADD / SUB-Befehl kann zwischen - erfolgen.
- Registrieren, um sich zu registrieren
- Speicher zum Registrieren
- Im Speicher registrieren
- Registrieren Sie sich zu konstanten Daten
- Speicher für konstante Daten
Wie bei anderen Anweisungen sind jedoch Speicher-zu-Speicher-Operationen mit ADD / SUB-Anweisungen nicht möglich. Eine ADD- oder SUB-Operation setzt oder löscht den Überlauf und überträgt Flags.
Beispiel
Im folgenden Beispiel werden zwei Ziffern vom Benutzer angefordert, die Ziffern im EAX- bzw. EBX-Register gespeichert, die Werte hinzugefügt, das Ergebnis an einem Speicherort ' res ' gespeichert und schließlich das Ergebnis angezeigt.
SYS_EXIT equ 1
SYS_READ equ 3
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1
segment .data
msg1 db "Enter a digit ", 0xA,0xD
len1 equ $- msg1 msg2 db "Please enter a second digit", 0xA,0xD len2 equ $- msg2
msg3 db "The sum is: "
len3 equ $- msg3
segment .bss
num1 resb 2
num2 resb 2
res resb 1
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80
mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num1
mov edx, 2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80
mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num2
mov edx, 2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80
; moving the first number to eax register and second number to ebx
; and subtracting ascii '0' to convert it into a decimal number
mov eax, [num1]
sub eax, '0'
mov ebx, [num2]
sub ebx, '0'
; add eax and ebx
add eax, ebx
; add '0' to to convert the sum from decimal to ASCII
add eax, '0'
; storing the sum in memory location res
mov [res], eax
; print the sum
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, res
mov edx, 1
int 0x80
exit:
mov eax, SYS_EXIT
xor ebx, ebx
int 0x80
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
Enter a digit:
3
Please enter a second digit:
4
The sum is:
7
The program with hardcoded variables −
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax,'3'
sub eax, '0'
mov ebx, '4'
sub ebx, '0'
add eax, ebx
add eax, '0'
mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,sum
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The sum is:", 0xA,0xD
len equ $ - msg
segment .bss
sum resb 1
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
The sum is:
7
Die MUL / IMUL-Anweisung
Es gibt zwei Anweisungen zum Multiplizieren von Binärdaten. Der MUL-Befehl (Multiply) verarbeitet vorzeichenlose Daten und der IMUL-Befehl (Integer Multiply) verarbeitet vorzeichenbehaftete Daten. Beide Anweisungen wirken sich auf das Carry- und Overflow-Flag aus.
Syntax
Die Syntax für die MUL / IMUL-Anweisungen lautet wie folgt:
MUL/IMUL multiplier
Der Multiplikand befindet sich in beiden Fällen in einem Akkumulator, abhängig von der Größe des Multiplikanden und des Multiplikators, und das erzeugte Produkt wird abhängig von der Größe der Operanden auch in zwei Registern gespeichert. Im folgenden Abschnitt werden die MUL-Anweisungen in drei verschiedenen Fällen erläutert:
Sr.Nr. | Szenarien |
---|---|
1 | When two bytes are multiplied − Der Multiplikand befindet sich im AL-Register, und der Multiplikator ist ein Byte im Speicher oder in einem anderen Register. Das Produkt ist in AX. Hochwertige 8 Bits des Produkts werden in AH und die niederwertigen 8 Bits in AL gespeichert.
|
2 | When two one-word values are multiplied − Der Multiplikand sollte sich im AX-Register befinden, und der Multiplikator ist ein Wort im Speicher oder ein anderes Register. Für eine Anweisung wie MUL DX müssen Sie beispielsweise den Multiplikator in DX und den Multiplikanden in AX speichern. Das resultierende Produkt ist ein Doppelwort, das zwei Register benötigt. Der Teil höherer Ordnung (ganz links) wird in DX und der Teil niedrigerer Ordnung (ganz rechts) in AX gespeichert.
|
3 | When two doubleword values are multiplied − Wenn zwei Doppelwortwerte multipliziert werden, sollte sich der Multiplikand in EAX befinden und der Multiplikator ist ein Doppelwortwert, der im Speicher oder in einem anderen Register gespeichert ist. Das erzeugte Produkt wird in den EDX: EAX-Registern gespeichert, dh die 32 Bits höherer Ordnung werden im EDX-Register gespeichert und die 32 Bits niedriger Ordnung werden im EAX-Register gespeichert.
|
Beispiel
MOV AL, 10
MOV DL, 25
MUL DL
...
MOV DL, 0FFH ; DL= -1
MOV AL, 0BEH ; AL = -66
IMUL DL
Beispiel
Das folgende Beispiel multipliziert 3 mit 2 und zeigt das Ergebnis an -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov al,'3'
sub al, '0'
mov bl, '2'
sub bl, '0'
mul bl
add al, '0'
mov [res], al
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,res
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The result is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
The result is:
6
Die DIV / IDIV-Anweisungen
Die Divisionsoperation erzeugt zwei Elemente - a quotient und ein remainder. Im Falle einer Multiplikation tritt kein Überlauf auf, da Register mit doppelter Länge verwendet werden, um das Produkt zu halten. Im Falle einer Teilung kann jedoch ein Überlauf auftreten. Der Prozessor erzeugt einen Interrupt, wenn ein Überlauf auftritt.
Der Befehl DIV (Divide) wird für vorzeichenlose Daten und der Befehl IDIV (Integer Divide) für vorzeichenbehaftete Daten verwendet.
Syntax
Das Format für die DIV / IDIV-Anweisung -
DIV/IDIV divisor
Die Dividende befindet sich in einem Akkumulator. Beide Anweisungen können mit 8-Bit-, 16-Bit- oder 32-Bit-Operanden arbeiten. Die Operation wirkt sich auf alle sechs Statusflags aus. Im folgenden Abschnitt werden drei Fälle von Division mit unterschiedlicher Operandengröße erläutert:
Sr.Nr. | Szenarien |
---|---|
1 | When the divisor is 1 byte − Es wird angenommen, dass sich die Dividende im AX-Register befindet (16 Bit). Nach der Division geht der Quotient in das AL-Register und der Rest in das AH-Register.
|
2 | When the divisor is 1 word − Die Dividende wird mit 32 Bit Länge und in den DX: AX-Registern angenommen. Die 16 Bits höherer Ordnung befinden sich in DX und die 16 Bits niedriger Ordnung befinden sich in AX. Nach der Division geht der 16-Bit-Quotient in das AX-Register und der 16-Bit-Rest in das DX-Register.
|
3 | When the divisor is doubleword − Die Dividende wird als 64 Bit lang und in den EDX: EAX-Registern angenommen. Die höherwertigen 32 Bit befinden sich in EDX und die niederwertigen 32 Bit in EAX. Nach der Division geht der 32-Bit-Quotient in das EAX-Register und der 32-Bit-Rest in das EDX-Register.
|
Beispiel
Das folgende Beispiel teilt 8 durch 2. Die dividend 8 ist in der gespeichert 16-bit AX register und die divisor 2 ist in der gespeichert 8-bit BL register.
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ax,'8'
sub ax, '0'
mov bl, '2'
sub bl, '0'
div bl
add ax, '0'
mov [res], ax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,res
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The result is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
The result is:
4
Der Prozessorbefehlssatz enthält die Befehle AND, OR, XOR, TEST und NOT Boolean Logic, die die Bits entsprechend den Anforderungen des Programms testen, setzen und löschen.
Das Format für diese Anleitung -
Sr.Nr. | Anweisung | Format |
---|---|---|
1 | UND | UND Operand1, Operand2 |
2 | ODER | ODER Operand1, Operand2 |
3 | XOR | XOR-Operand1, Operand2 |
4 | PRÜFUNG | TEST operand1, operand2 |
5 | NICHT | NICHT operand1 |
Der erste Operand kann sich in allen Fällen entweder im Register oder im Speicher befinden. Der zweite Operand kann sich entweder im Register / Speicher oder in einem unmittelbaren (konstanten) Wert befinden. Speicher-zu-Speicher-Operationen sind jedoch nicht möglich. Diese Anweisungen vergleichen oder stimmen Bits der Operanden ab und setzen die Flags CF, OF, PF, SF und ZF.
Die AND-Anweisung
Der AND-Befehl wird zur Unterstützung logischer Ausdrücke verwendet, indem eine bitweise AND-Operation ausgeführt wird. Die bitweise AND-Operation gibt 1 zurück, wenn die übereinstimmenden Bits von beiden Operanden 1 sind, andernfalls gibt sie 0 zurück. Zum Beispiel -
Operand1: 0101
Operand2: 0011
----------------------------
After AND -> Operand1: 0001
Die UND-Operation kann zum Löschen eines oder mehrerer Bits verwendet werden. Angenommen, das BL-Register enthält 0011 1010. Wenn Sie die höherwertigen Bits auf Null löschen müssen, UND-Verknüpfung mit 0FH.
AND BL, 0FH ; This sets BL to 0000 1010
Nehmen wir ein anderes Beispiel. Wenn Sie überprüfen möchten, ob eine bestimmte Zahl ungerade oder gerade ist, besteht ein einfacher Test darin, das niedrigstwertige Bit der Zahl zu überprüfen. Wenn dies 1 ist, ist die Zahl ungerade, andernfalls ist die Zahl gerade.
Angenommen, die Nummer befindet sich im AL-Register, können wir schreiben -
AND AL, 01H ; ANDing with 0000 0001
JZ EVEN_NUMBER
Das folgende Programm veranschaulicht dies -
Beispiel
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ax, 8h ;getting 8 in the ax
and ax, 1 ;and ax with 1
jz evnn
mov eax, 4 ;system call number (sys_write)
mov ebx, 1 ;file descriptor (stdout)
mov ecx, odd_msg ;message to write
mov edx, len2 ;length of message
int 0x80 ;call kernel
jmp outprog
evnn:
mov ah, 09h
mov eax, 4 ;system call number (sys_write)
mov ebx, 1 ;file descriptor (stdout)
mov ecx, even_msg ;message to write
mov edx, len1 ;length of message
int 0x80 ;call kernel
outprog:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
even_msg db 'Even Number!' ;message showing even number
len1 equ $ - even_msg odd_msg db 'Odd Number!' ;message showing odd number len2 equ $ - odd_msg
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
Even Number!
Ändern Sie den Wert im Axtregister mit einer ungeraden Ziffer wie -
mov ax, 9h ; getting 9 in the ax
Das Programm würde Folgendes anzeigen:
Odd Number!
Ebenso können Sie das gesamte Register mit 00H UND löschen.
Die OP-Anweisung
Der OR-Befehl wird zur Unterstützung des logischen Ausdrucks durch Ausführen einer bitweisen OR-Operation verwendet. Der bitweise ODER-Operator gibt 1 zurück, wenn die übereinstimmenden Bits von einem oder beiden Operanden eins sind. Es gibt 0 zurück, wenn beide Bits Null sind.
Zum Beispiel,
Operand1: 0101
Operand2: 0011
----------------------------
After OR -> Operand1: 0111
Die ODER-Verknüpfung kann zum Setzen eines oder mehrerer Bits verwendet werden. Nehmen wir zum Beispiel an, das AL-Register enthält 0011 1010, Sie müssen die vier niederwertigen Bits setzen, Sie können es ODER mit einem Wert 0000 1111, dh FH, ODER.
OR BL, 0FH ; This sets BL to 0011 1111
Beispiel
Das folgende Beispiel zeigt die ODER-Anweisung. Speichern wir den Wert 5 und 3 in den Registern AL und BL, dann den Befehl,
OR AL, BL
sollte 7 im AL-Register speichern -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov al, 5 ;getting 5 in the al
mov bl, 3 ;getting 3 in the bl
or al, bl ;or al and bl registers, result should be 7
add al, byte '0' ;converting decimal to ascii
mov [result], al
mov eax, 4
mov ebx, 1
mov ecx, result
mov edx, 1
int 0x80
outprog:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .bss
result resb 1
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
7
Die XOR-Anweisung
Der XOR-Befehl implementiert die bitweise XOR-Operation. Die XOR-Operation setzt das resultierende Bit genau dann auf 1, wenn sich die Bits von den Operanden unterscheiden. Wenn die Bits von den Operanden gleich sind (beide 0 oder beide 1), wird das resultierende Bit auf 0 gelöscht.
Zum Beispiel,
Operand1: 0101
Operand2: 0011
----------------------------
After XOR -> Operand1: 0110
XORing Ein Operand mit sich selbst ändert den Operanden in 0. Dies wird verwendet, um ein Register zu löschen.
XOR EAX, EAX
Die TEST-Anweisung
Der TEST-Befehl funktioniert genauso wie die AND-Operation, ändert jedoch im Gegensatz zum AND-Befehl nicht den ersten Operanden. Wenn wir also prüfen müssen, ob eine Zahl in einem Register gerade oder ungerade ist, können wir dies auch mit der Anweisung TEST tun, ohne die ursprüngliche Zahl zu ändern.
TEST AL, 01H
JZ EVEN_NUMBER
Die NOT-Anweisung
Der NOT-Befehl implementiert die bitweise NOT-Operation. Die Operation NOT kehrt die Bits in einem Operanden um. Der Operand kann sich entweder in einem Register oder im Speicher befinden.
Zum Beispiel,
Operand1: 0101 0011
After NOT -> Operand1: 1010 1100
Die bedingte Ausführung in Assemblersprache wird durch mehrere Schleifen- und Verzweigungsanweisungen erreicht. Diese Anweisungen können den Steuerungsfluss in einem Programm ändern. Die bedingte Ausführung wird in zwei Szenarien beobachtet:
Sr.Nr. | Bedingte Anweisungen |
---|---|
1 | Unconditional jump Dies wird durch die JMP-Anweisung ausgeführt. Die bedingte Ausführung beinhaltet häufig eine Übertragung der Steuerung an die Adresse eines Befehls, der nicht dem aktuell ausgeführten Befehl folgt. Die Übertragung der Steuerung kann vorwärts erfolgen, um einen neuen Befehlssatz auszuführen, oder rückwärts, um dieselben Schritte erneut auszuführen. |
2 | Conditional jump Dies wird durch einen Satz von Sprungbefehlen j <Bedingung> abhängig von der Bedingung ausgeführt. Die bedingten Anweisungen übertragen die Steuerung, indem sie den sequentiellen Fluss unterbrechen, und sie tun dies, indem sie den Versatzwert in IP ändern. |
Lassen Sie uns die CMP-Anweisung besprechen, bevor wir die bedingten Anweisungen besprechen.
CMP-Anweisung
Der CMP-Befehl vergleicht zwei Operanden. Es wird im Allgemeinen bei der bedingten Ausführung verwendet. Diese Anweisung subtrahiert grundsätzlich einen Operanden vom anderen, um zu vergleichen, ob die Operanden gleich sind oder nicht. Die Ziel- oder Quelloperanden werden nicht gestört. Es wird zusammen mit der bedingten Sprunganweisung zur Entscheidungsfindung verwendet.
Syntax
CMP destination, source
CMP vergleicht zwei numerische Datenfelder. Der Zieloperand kann sich entweder im Register oder im Speicher befinden. Der Quelloperand kann ein konstantes (unmittelbares) Daten-, Register- oder Speicherelement sein.
Beispiel
CMP DX, 00 ; Compare the DX value with zero
JE L7 ; If yes, then jump to label L7
.
.
L7: ...
CMP wird häufig verwendet, um zu vergleichen, ob ein Zählerwert die Häufigkeit erreicht hat, mit der eine Schleife ausgeführt werden muss. Betrachten Sie den folgenden typischen Zustand:
INC EDX
CMP EDX, 10 ; Compares whether the counter has reached 10
JLE LP1 ; If it is less than or equal to 10, then jump to LP1
Bedingungsloser Sprung
Wie bereits erwähnt, wird dies von der JMP-Anweisung ausgeführt. Die bedingte Ausführung beinhaltet häufig eine Übertragung der Steuerung an die Adresse eines Befehls, der nicht dem aktuell ausgeführten Befehl folgt. Die Übertragung der Steuerung kann vorwärts erfolgen, um einen neuen Befehlssatz auszuführen, oder rückwärts, um dieselben Schritte erneut auszuführen.
Syntax
Der JMP-Befehl enthält einen Labelnamen, bei dem der Kontrollfluss sofort übertragen wird. Die Syntax der JMP-Anweisung lautet -
JMP label
Beispiel
Das folgende Codeausschnitt veranschaulicht die JMP-Anweisung -
MOV AX, 00 ; Initializing AX to 0
MOV BX, 00 ; Initializing BX to 0
MOV CX, 01 ; Initializing CX to 1
L20:
ADD AX, 01 ; Increment AX
ADD BX, AX ; Add AX to BX
SHL CX, 1 ; shift left CX, this in turn doubles the CX value
JMP L20 ; repeats the statements
Bedingter Sprung
Wenn eine bestimmte Bedingung im bedingten Sprung erfüllt ist, wird der Steuerfluss an einen Zielbefehl übertragen. Abhängig von der Bedingung und den Daten gibt es zahlreiche Anweisungen für bedingte Sprünge.
Im Folgenden sind die Anweisungen für bedingte Sprünge aufgeführt, die für vorzeichenbehaftete Daten verwendet werden, die für arithmetische Operationen verwendet werden.
Anweisung | Beschreibung | Flaggen getestet |
---|---|---|
JE / JZ | Jump Equal oder Jump Zero | ZF |
JNE / JNZ | Springe nicht gleich oder springe nicht Null | ZF |
JG / JNLE | Springe größer oder springe nicht weniger / gleich | OF, SF, ZF |
JGE / JNL | Springe größer / gleich oder springe nicht weniger | OF, SF |
JL / JNGE | Weniger springen oder nicht größer / gleich springen | OF, SF |
JLE / JNG | Weniger / gleich springen oder nicht größer springen | OF, SF, ZF |
Im Folgenden sind die Anweisungen für bedingte Sprünge aufgeführt, die für vorzeichenlose Daten verwendet werden, die für logische Operationen verwendet werden.
Anweisung | Beschreibung | Flaggen getestet |
---|---|---|
JE / JZ | Jump Equal oder Jump Zero | ZF |
JNE / JNZ | Springe nicht gleich oder springe nicht Null | ZF |
JA / JNBE | Über oder über nicht unter / gleich springen | CF, ZF |
JAE / JNB | Springe über / gleich oder springe nicht unter | CF. |
JB / JNAE | Springe nach unten oder springe nicht nach oben / gleich | CF. |
JBE / JNA | Springe unter / gleich oder springe nicht über | AF, CF. |
Die folgenden Anweisungen für bedingte Sprünge haben spezielle Verwendungszwecke und überprüfen den Wert von Flags -
Anweisung | Beschreibung | Flaggen getestet |
---|---|---|
JXCZ | Springe, wenn CX Null ist | keiner |
JC | Springen, wenn tragen | CF. |
JNC | Springen, wenn kein Tragen | CF. |
JO | Bei Überlauf springen | VON |
JNO | Springen, wenn kein Überlauf vorliegt | VON |
JP / JPE | Jump Parity oder Jump Parity Even | PF |
JNP / JPO | Jump No Parity oder Jump Parity Odd | PF |
JS | Sprungzeichen (negativer Wert) | SF |
JNS | Kein Vorzeichen springen (positiver Wert) | SF |
Die Syntax für den Befehlssatz J <Bedingung> -
Beispiel,
CMP AL, BL
JE EQUAL
CMP AL, BH
JE EQUAL
CMP AL, CL
JE EQUAL
NON_EQUAL: ...
EQUAL: ...
Beispiel
Das folgende Programm zeigt die größte von drei Variablen an. Die Variablen sind zweistellige Variablen. Die drei Variablen num1, num2 und num3 haben die Werte 47, 22 bzw. 31 -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ecx, [num1]
cmp ecx, [num2]
jg check_third_num
mov ecx, [num2]
check_third_num:
cmp ecx, [num3]
jg _exit
mov ecx, [num3]
_exit:
mov [largest], ecx
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,largest
mov edx, 2
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1
int 80h
section .data
msg db "The largest digit is: ", 0xA,0xD
len equ $- msg
num1 dd '47'
num2 dd '22'
num3 dd '31'
segment .bss
largest resb 2
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
The largest digit is:
47
Der JMP-Befehl kann zum Implementieren von Schleifen verwendet werden. Beispielsweise kann das folgende Codeausschnitt zum zehnmaligen Ausführen des Schleifenkörpers verwendet werden.
MOV CL, 10
L1:
<LOOP-BODY>
DEC CL
JNZ L1
Der Prozessorbefehlssatz enthält jedoch eine Gruppe von Schleifenbefehlen zum Implementieren der Iteration. Der grundlegende LOOP-Befehl hat die folgende Syntax:
LOOP label
Dabei ist label das Zieletikett, das die Zielanweisung wie in den Sprunganweisungen identifiziert. Der LOOP-Befehl geht davon aus, dass dieECX register contains the loop count. Wenn der Schleifenbefehl ausgeführt wird, wird das ECX-Register dekrementiert und die Steuerung springt zum Zieletikett, bis der ECX-Registerwert, dh der Zähler, den Wert Null erreicht.
Das obige Code-Snippet könnte geschrieben werden als -
mov ECX,10
l1:
<loop body>
loop l1
Beispiel
Das folgende Programm druckt die Nummern 1 bis 9 auf dem Bildschirm -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ecx,10
mov eax, '1'
l1:
mov [num], eax
mov eax, 4
mov ebx, 1
push ecx
mov ecx, num
mov edx, 1
int 0x80
mov eax, [num]
sub eax, '0'
inc eax
add eax, '0'
pop ecx
loop l1
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .bss
num resb 1
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
123456789:
Numerische Daten werden im Allgemeinen im Binärsystem dargestellt. Arithmetische Anweisungen arbeiten mit Binärdaten. Wenn Zahlen auf dem Bildschirm angezeigt oder über die Tastatur eingegeben werden, liegen sie in ASCII-Form vor.
Bisher haben wir diese Eingabedaten für arithmetische Berechnungen in ASCII-Form in Binär konvertiert und das Ergebnis wieder in Binär konvertiert. Der folgende Code zeigt dies -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax,'3'
sub eax, '0'
mov ebx, '4'
sub ebx, '0'
add eax, ebx
add eax, '0'
mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,sum
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The sum is:", 0xA,0xD
len equ $ - msg
segment .bss
sum resb 1
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
The sum is:
7
Solche Konvertierungen sind jedoch mit einem Overhead verbunden, und die Assembler-Programmierung ermöglicht eine effizientere Verarbeitung von Zahlen in binärer Form. Dezimalzahlen können in zwei Formen dargestellt werden -
- ASCII-Formular
- BCD oder binär codierte Dezimalform
ASCII-Darstellung
In der ASCII-Darstellung werden Dezimalzahlen als Zeichenfolge von ASCII-Zeichen gespeichert. Beispielsweise wird der Dezimalwert 1234 als - gespeichert
31 32 33 34H
Dabei ist 31H der ASCII-Wert für 1, 32H der ASCII-Wert für 2 und so weiter. Es gibt vier Anweisungen zum Verarbeiten von Nummern in der ASCII-Darstellung:
AAA - ASCII-Anpassung nach Zugabe
AAS - ASCII-Anpassung nach Subtraktion
AAM - ASCII-Anpassung nach Multiplikation
AAD - ASCII-Anpassung vor Division
Diese Anweisungen nehmen keine Operanden an und setzen voraus, dass sich der erforderliche Operand im AL-Register befindet.
Das folgende Beispiel verwendet die AAS-Anweisung, um das Konzept zu demonstrieren -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
sub ah, ah
mov al, '9'
sub al, '3'
aas
or al, 30h
mov [res], ax
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,1 ;message length
mov ecx,res ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'The Result is:',0xa
len equ $ - msg
section .bss
res resb 1
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
The Result is:
6
BCD-Vertretung
Es gibt zwei Arten der BCD-Darstellung:
- Entpackte BCD-Darstellung
- Gepackte BCD-Darstellung
In der entpackten BCD-Darstellung speichert jedes Byte das binäre Äquivalent einer Dezimalstelle. Beispielsweise wird die Nummer 1234 als - gespeichert
01 02 03 04H
Es gibt zwei Anweisungen zum Verarbeiten dieser Nummern -
AAM - ASCII-Anpassung nach Multiplikation
AAD - ASCII-Anpassung vor Division
Die vier ASCII-Anpassungsanweisungen AAA, AAS, AAM und AAD können auch mit entpackter BCD-Darstellung verwendet werden. In der gepackten BCD-Darstellung wird jede Ziffer mit vier Bits gespeichert. Zwei Dezimalstellen werden in ein Byte gepackt. Beispielsweise wird die Nummer 1234 als - gespeichert
12 34H
Es gibt zwei Anweisungen zum Verarbeiten dieser Nummern -
DAA - Dezimalanpassung nach Addition
DAS - Dezimalanpassung nach Subtraktion
Es gibt keine Unterstützung für Multiplikation und Division in der gepackten BCD-Darstellung.
Beispiel
Das folgende Programm addiert zwei 5-stellige Dezimalzahlen und zeigt die Summe an. Es verwendet die oben genannten Konzepte -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov esi, 4 ;pointing to the rightmost digit
mov ecx, 5 ;num of digits
clc
add_loop:
mov al, [num1 + esi]
adc al, [num2 + esi]
aaa
pushf
or al, 30h
popf
mov [sum + esi], al
dec esi
loop add_loop
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,5 ;message length
mov ecx,sum ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'The Sum is:',0xa
len equ $ - msg
num1 db '12345'
num2 db '23456'
sum db ' '
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
The Sum is:
35801
In unseren vorherigen Beispielen haben wir bereits Zeichenfolgen mit variabler Länge verwendet. Die Zeichenfolgen variabler Länge können beliebig viele Zeichen enthalten. Im Allgemeinen geben wir die Länge der Zeichenfolge auf eine der beiden Arten an:
- Zeichenfolgenlänge explizit speichern
- Verwenden eines Sentinel-Zeichens
Wir können die Zeichenfolgenlänge explizit speichern, indem wir das Symbol $ location counter verwenden, das den aktuellen Wert des Standortzählers darstellt. Im folgenden Beispiel -
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
$ zeigt auf das Byte nach dem letzten Zeichen der Zeichenfolgenvariablen msg . Deshalb,$-msggibt die Länge der Zeichenfolge an. Wir können auch schreiben
msg db 'Hello, world!',0xa ;our dear string
len equ 13 ;length of our dear string
Alternativ können Sie Zeichenfolgen mit einem nachgestellten Sentinel-Zeichen speichern, um eine Zeichenfolge abzugrenzen, anstatt die Zeichenfolgenlänge explizit zu speichern. Das Sentinel-Zeichen sollte ein Sonderzeichen sein, das nicht in einer Zeichenfolge enthalten ist.
Zum Beispiel -
message DB 'I am loving it!', 0
String-Anweisungen
Jeder Zeichenfolgenbefehl kann einen Quelloperanden, einen Zieloperanden oder beides erfordern. Bei 32-Bit-Segmenten verwenden Zeichenfolgenbefehle ESI- und EDI-Register, um auf die Quell- bzw. Zieloperanden zu verweisen.
Für 16-Bit-Segmente werden jedoch die SI- und DI-Register verwendet, um auf die Quelle bzw. das Ziel zu zeigen.
Es gibt fünf grundlegende Anweisungen zum Verarbeiten von Zeichenfolgen. Sie sind -
MOVS - Diese Anweisung verschiebt 1 Byte, Word oder Doubleword von Daten vom Speicherort zu einem anderen.
LODS- Diese Anweisung wird aus dem Speicher geladen. Wenn der Operand ein Byte hat, wird er in das AL-Register geladen, wenn der Operand ein Wort ist, wird er in das AX-Register geladen und ein Doppelwort wird in das EAX-Register geladen.
STOS - Dieser Befehl speichert Daten aus dem Register (AL, AX oder EAX) im Speicher.
CMPS- Diese Anweisung vergleicht zwei Datenelemente im Speicher. Daten können eine Bytegröße, ein Wort oder ein Doppelwort haben.
SCAS - Diese Anweisung vergleicht den Inhalt eines Registers (AL, AX oder EAX) mit dem Inhalt eines Elements im Speicher.
Jeder der obigen Befehle hat eine Byte-, Wort- und Doppelwortversion, und Zeichenfolgenbefehle können unter Verwendung eines Wiederholungspräfixes wiederholt werden.
Diese Anweisungen verwenden das Registerpaar ES: DI und DS: SI, wobei DI- und SI-Register gültige Versatzadressen enthalten, die sich auf im Speicher gespeicherte Bytes beziehen. SI ist normalerweise mit DS (Datensegment) verbunden und DI ist immer mit ES (zusätzliches Segment) verbunden.
Die Register DS: SI (oder ESI) und ES: DI (oder EDI) zeigen auf die Quell- bzw. Zieloperanden. Es wird angenommen, dass sich der Quelloperand bei DS: SI (oder ESI) und der Zieloperand bei ES: DI (oder EDI) im Speicher befindet.
Für 16-Bit-Adressen werden die SI- und DI-Register verwendet, und für 32-Bit-Adressen werden die ESI- und EDI-Register verwendet.
Die folgende Tabelle enthält verschiedene Versionen von Zeichenfolgenanweisungen und den angenommenen Platz der Operanden.
Grundlegende Anweisung | Operanden bei | Byte-Betrieb | Wortoperation | Doppelwortoperation |
---|---|---|---|---|
MOVS | ES: DI, DS: SI | MOVSB | MOVSW | MOVSD |
LODS | AX, DS: SI | LODSB | LODSW | LODSD |
STOS | ES: DI, AX | STOSB | STOSW | STOSD |
CMPS | DS: SI, ES: DI | CMPSB | CMPSW | CMPSD |
SCAS | ES: DI, AX | SCASB | SCASW | SCASD |
Wiederholungspräfixe
Wenn das REP-Präfix vor einem Zeichenfolgenbefehl gesetzt wird, z. B. REP MOVSB, wird der Befehl basierend auf einem im CX-Register platzierten Zähler wiederholt. REP führt den Befehl aus, verringert CX um 1 und prüft, ob CX Null ist. Es wiederholt die Befehlsverarbeitung, bis CX Null ist.
Das Richtungsflag (DF) bestimmt die Richtung der Operation.
- Verwenden Sie CLD (Clear Direction Flag, DF = 0), um die Operation von links nach rechts durchzuführen.
- Verwenden Sie STD (Set Direction Flag, DF = 1), um die Operation von rechts nach links durchzuführen.
Das REP-Präfix weist auch die folgenden Variationen auf:
REP: Es ist die bedingungslose Wiederholung. Der Vorgang wird wiederholt, bis CX Null ist.
REPE oder REPZ: Dies ist eine bedingte Wiederholung. Es wiederholt den Vorgang, während das Null-Flag gleich / Null anzeigt. Es stoppt, wenn der ZF ungleich / Null anzeigt oder wenn CX Null ist.
REPNE oder REPNZ: Dies ist auch eine bedingte Wiederholung. Es wiederholt die Operation, während das Null-Flag ungleich / Null anzeigt. Es stoppt, wenn der ZF gleich / Null anzeigt oder wenn CX auf Null dekrementiert wird.
Wir haben bereits diskutiert, dass die Datendefinitionsanweisungen an den Assembler zum Zuweisen von Speicher für Variablen verwendet werden. Die Variable könnte auch mit einem bestimmten Wert initialisiert werden. Der initialisierte Wert kann in hexadezimaler, dezimaler oder binärer Form angegeben werden.
Zum Beispiel können wir eine Wortvariable 'Monate' auf eine der folgenden Arten definieren:
MONTHS DW 12
MONTHS DW 0CH
MONTHS DW 0110B
Die Datendefinitionsanweisungen können auch zum Definieren eines eindimensionalen Arrays verwendet werden. Definieren wir ein eindimensionales Array von Zahlen.
NUMBERS DW 34, 45, 56, 67, 75, 89
Die obige Definition deklariert ein Array von sechs Wörtern, die jeweils mit den Nummern 34, 45, 56, 67, 75, 89 initialisiert sind. Dies weist 2x6 = 12 Bytes aufeinanderfolgenden Speicherplatzes zu. Die symbolische Adresse der ersten Nummer lautet NUMBERS und die der zweiten Nummer lautet NUMBERS + 2 usw.
Nehmen wir ein anderes Beispiel. Sie können ein Array mit dem Namen Inventar der Größe 8 definieren und alle Werte mit Null initialisieren, als -
INVENTORY DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
Was abgekürzt werden kann als -
INVENTORY DW 0, 0 , 0 , 0 , 0 , 0 , 0 , 0
Die TIMES-Direktive kann auch für mehrere Initialisierungen mit demselben Wert verwendet werden. Mit TIMES kann das INVENTORY-Array wie folgt definiert werden:
INVENTORY TIMES 8 DW 0
Beispiel
Das folgende Beispiel demonstriert die obigen Konzepte durch Definieren eines 3-Element-Arrays x, in dem drei Werte gespeichert sind: 2, 3 und 4. Es fügt die Werte im Array hinzu und zeigt die Summe 9 - an.
section .text
global _start ;must be declared for linker (ld)
_start:
mov eax,3 ;number bytes to be summed
mov ebx,0 ;EBX will store the sum
mov ecx, x ;ECX will point to the current element to be summed
top: add ebx, [ecx]
add ecx,1 ;move pointer to next element
dec eax ;decrement counter
jnz top ;if counter not 0, then loop again
done:
add ebx, '0'
mov [sum], ebx ;done, store result in "sum"
display:
mov edx,1 ;message length
mov ecx, sum ;message to write
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
global x
x:
db 2
db 4
db 3
sum:
db 0
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
9
Prozeduren oder Unterprogramme sind in der Assemblersprache sehr wichtig, da die Assemblersprachenprogramme in der Regel groß sind. Prozeduren sind durch einen Namen gekennzeichnet. Nach diesem Namen wird der Hauptteil der Prozedur beschrieben, die eine genau definierte Aufgabe ausführt. Das Ende des Vorgangs wird durch eine return-Anweisung angezeigt.
Syntax
Es folgt die Syntax zum Definieren einer Prozedur:
proc_name:
procedure body
...
ret
Die Prozedur wird von einer anderen Funktion mit der Anweisung CALL aufgerufen. Die CALL-Anweisung sollte den Namen der aufgerufenen Prozedur als Argument haben, wie unten gezeigt -
CALL proc_name
Die aufgerufene Prozedur gibt die Steuerung unter Verwendung der RET-Anweisung an die aufrufende Prozedur zurück.
Beispiel
Schreiben wir eine sehr einfache Prozedur namens sum , die die im ECX- und EDX-Register gespeicherten Variablen addiert und die Summe im EAX-Register zurückgibt.
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ecx,'4'
sub ecx, '0'
mov edx, '5'
sub edx, '0'
call sum ;call sum procedure
mov [res], eax
mov ecx, msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx, res
mov edx, 1
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
sum:
mov eax, ecx
add eax, edx
add eax, '0'
ret
section .data
msg db "The sum is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
The sum is:
9
Stapelt Datenstruktur
Ein Stapel ist eine Array-ähnliche Datenstruktur im Speicher, in der Daten gespeichert und von einem Ort entfernt werden können, der als "Oberseite" des Stapels bezeichnet wird. Die Daten, die gespeichert werden müssen, werden in den Stapel "geschoben" und die abzurufenden Daten werden aus dem Stapel "herausgeschleudert". Der Stapel ist eine LIFO-Datenstruktur, dh die zuerst gespeicherten Daten werden zuletzt abgerufen.
Die Assemblersprache enthält zwei Anweisungen für Stapeloperationen: PUSH und POP. Diese Anweisungen haben Syntaxen wie -
PUSH operand
POP address/register
Der im Stapelsegment reservierte Speicherplatz wird zum Implementieren des Stapels verwendet. Die Register SS und ESP (oder SP) werden zur Implementierung des Stapels verwendet. Auf die Oberseite des Stapels, die auf das letzte in den Stapel eingefügte Datenelement zeigt, zeigt das SS: ESP-Register, wobei das SS-Register auf den Anfang des Stapelsegments zeigt und der SP (oder ESP) den Versatz in angibt das Stapelsegment.
Die Stapelimplementierung weist die folgenden Merkmale auf:
Nur words oder doublewords könnte im Stapel gespeichert werden, kein Byte.
Der Stapel wächst in umgekehrter Richtung, dh in Richtung der unteren Speicheradresse
Die Oberseite des Stapels zeigt auf das zuletzt in den Stapel eingefügte Element. es zeigt auf das untere Byte des zuletzt eingefügten Wortes.
Wie wir über das Speichern der Werte der Register im Stapel besprochen haben, bevor sie für eine bestimmte Verwendung verwendet wurden; es kann folgendermaßen geschehen:
; Save the AX and BX registers in the stack
PUSH AX
PUSH BX
; Use the registers for other purpose
MOV AX, VALUE1
MOV BX, VALUE2
...
MOV VALUE1, AX
MOV VALUE2, BX
; Restore the original values
POP BX
POP AX
Beispiel
Das folgende Programm zeigt den gesamten ASCII-Zeichensatz an. Das Hauptprogramm ruft eine Prozedur namens display auf , die den ASCII-Zeichensatz anzeigt.
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
call display
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
display:
mov ecx, 256
next:
push ecx
mov eax, 4
mov ebx, 1
mov ecx, achar
mov edx, 1
int 80h
pop ecx
mov dx, [achar]
cmp byte [achar], 0dh
inc byte [achar]
loop next
ret
section .data
achar db '0'
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
...
...
Eine rekursive Prozedur ruft sich selbst auf. Es gibt zwei Arten von Rekursionen: direkte und indirekte. Bei der direkten Rekursion ruft sich die Prozedur selbst auf, und bei der indirekten Rekursion ruft die erste Prozedur eine zweite Prozedur auf, die wiederum die erste Prozedur aufruft.
Rekursion konnte in zahlreichen mathematischen Algorithmen beobachtet werden. Betrachten Sie beispielsweise den Fall der Berechnung der Fakultät einer Zahl. Faktor einer Zahl ist gegeben durch die Gleichung -
Fact (n) = n * fact (n-1) for n > 0
Beispiel: Fakultät von 5 ist 1 x 2 x 3 x 4 x 5 = 5 x Fakultät von 4, und dies kann ein gutes Beispiel für die Darstellung einer rekursiven Prozedur sein. Jeder rekursive Algorithmus muss eine Endbedingung haben, dh der rekursive Aufruf des Programms sollte gestoppt werden, wenn eine Bedingung erfüllt ist. Im Fall eines faktoriellen Algorithmus ist die Endbedingung erreicht, wenn n 0 ist.
Das folgende Programm zeigt, wie Fakultät n in Assemblersprache implementiert ist. Um das Programm einfach zu halten, berechnen wir Fakultät 3.
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov bx, 3 ;for calculating factorial 3
call proc_fact
add ax, 30h
mov [fact], ax
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,1 ;message length
mov ecx,fact ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
proc_fact:
cmp bl, 1
jg do_calculation
mov ax, 1
ret
do_calculation:
dec bl
call proc_fact
inc bl
mul bl ;ax = al * bl
ret
section .data
msg db 'Factorial 3 is:',0xa
len equ $ - msg
section .bss
fact resb 1
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
Factorial 3 is:
6
Das Schreiben eines Makros ist eine weitere Möglichkeit, die modulare Programmierung in Assemblersprache sicherzustellen.
Ein Makro ist eine Folge von Anweisungen, die durch einen Namen zugewiesen werden und an einer beliebigen Stelle im Programm verwendet werden können.
In NASM werden Makros mit definiert %macro und %endmacro Richtlinien.
Das Makro beginnt mit der% -Makro-Direktive und endet mit der% endmacro-Direktive.
Die Syntax für die Makrodefinition -
%macro macro_name number_of_params
<macro body>
%endmacro
Wobei number_of_params die Nummernparameter angibt, macro_name den Namen des Makros angibt.
Das Makro wird aufgerufen, indem der Makroname zusammen mit den erforderlichen Parametern verwendet wird. Wenn Sie eine Folge von Anweisungen in einem Programm mehrmals verwenden müssen, können Sie diese Anweisungen in ein Makro einfügen und verwenden, anstatt die Anweisungen ständig zu schreiben.
Beispielsweise besteht ein sehr häufiger Bedarf an Programmen darin, eine Zeichenfolge auf dem Bildschirm zu schreiben. Zum Anzeigen einer Zeichenfolge benötigen Sie die folgende Folge von Anweisungen:
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
Im obigen Beispiel für die Anzeige einer Zeichenfolge wurden die Register EAX, EBX, ECX und EDX vom Funktionsaufruf INT 80H verwendet. Jedes Mal, wenn Sie auf dem Bildschirm anzeigen müssen, müssen Sie diese Register auf dem Stapel speichern, INT 80H aufrufen und dann den ursprünglichen Wert der Register aus dem Stapel wiederherstellen. Daher kann es nützlich sein, zwei Makros zum Speichern und Wiederherstellen von Daten zu schreiben.
Wir haben festgestellt, dass einige Anweisungen wie IMUL, IDIV, INT usw. einige der Informationen benötigen, um in bestimmten Registern gespeichert zu werden, und sogar Werte in bestimmten Registern zurückgeben müssen. Wenn das Programm diese Register bereits zum Speichern wichtiger Daten verwendet hat, sollten die vorhandenen Daten aus diesen Registern im Stapel gespeichert und nach Ausführung des Befehls wiederhergestellt werden.
Beispiel
Das folgende Beispiel zeigt das Definieren und Verwenden von Makros -
; A macro with two parameters
; Implements the write system call
%macro write_string 2
mov eax, 4
mov ebx, 1
mov ecx, %1
mov edx, %2
int 80h
%endmacro
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
write_string msg1, len1
write_string msg2, len2
write_string msg3, len3
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg1 db 'Hello, programmers!',0xA,0xD
len1 equ $ - msg1 msg2 db 'Welcome to the world of,', 0xA,0xD len2 equ $- msg2
msg3 db 'Linux assembly programming! '
len3 equ $- msg3
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
Das System betrachtet alle Eingabe- oder Ausgabedaten als Bytestrom. Es gibt drei Standarddateistreams -
- Standardeingabe (stdin),
- Standardausgabe (stdout) und
- Standardfehler (stderr).
Dateideskriptor
EIN file descriptorist eine 16-Bit-Ganzzahl, die einer Datei als Datei-ID zugewiesen ist. Wenn eine neue Datei erstellt oder eine vorhandene Datei geöffnet wird, wird der Dateideskriptor für den Zugriff auf die Datei verwendet.
Dateideskriptor der Standarddateistreams - stdin, stdout und stderr sind 0, 1 bzw. 2.
Dateizeiger
EIN file pointerGibt den Speicherort für eine nachfolgende Lese- / Schreiboperation in der Datei in Bytes an. Jede Datei wird als Folge von Bytes betrachtet. Jede geöffnete Datei ist einem Dateizeiger zugeordnet, der einen Versatz in Byte relativ zum Dateianfang angibt. Wenn eine Datei geöffnet wird, wird der Dateizeiger auf Null gesetzt.
Systemaufrufe für die Dateiverwaltung
In der folgenden Tabelle werden die Systemaufrufe im Zusammenhang mit der Dateiverwaltung kurz beschrieben.
% eax | Name | % ebx | % ecx | % edx |
---|---|---|---|---|
2 | sys_fork | struct pt_regs | - - | - - |
3 | sys_read | unsigned int | char * | size_t |
4 | sys_write | unsigned int | const char * | size_t |
5 | sys_open | const char * | int | int |
6 | sys_close | unsigned int | - - | - - |
8 | sys_creat | const char * | int | - - |
19 | sys_lseek | unsigned int | off_t | unsigned int |
Die für die Verwendung der Systemaufrufe erforderlichen Schritte sind dieselben, wie wir zuvor erläutert haben -
- Tragen Sie die Systemrufnummer in das EAX-Register ein.
- Speichern Sie die Argumente für den Systemaufruf in den Registern EBX, ECX usw.
- Rufen Sie den entsprechenden Interrupt an (80h).
- Das Ergebnis wird normalerweise im EAX-Register zurückgegeben.
Erstellen und Öffnen einer Datei
Führen Sie zum Erstellen und Öffnen einer Datei die folgenden Aufgaben aus:
- Fügen Sie den Systemaufruf sys_creat () Nummer 8 in das EAX-Register ein.
- Tragen Sie den Dateinamen in das EBX-Register ein.
- Fügen Sie die Dateiberechtigungen in das ECX-Register ein.
Der Systemaufruf gibt den Dateideskriptor der erstellten Datei im EAX-Register zurück. Im Fehlerfall befindet sich der Fehlercode im EAX-Register.
Vorhandene Datei öffnen
Führen Sie zum Öffnen einer vorhandenen Datei die folgenden Aufgaben aus:
- Tragen Sie den Systemaufruf sys_open () Nummer 5 in das EAX-Register ein.
- Tragen Sie den Dateinamen in das EBX-Register ein.
- Stellen Sie den Dateizugriffsmodus in das ECX-Register.
- Fügen Sie die Dateiberechtigungen in das EDX-Register ein.
Der Systemaufruf gibt den Dateideskriptor der erstellten Datei im EAX-Register zurück. Im Fehlerfall befindet sich der Fehlercode im EAX-Register.
Unter den Dateizugriffsmodi werden am häufigsten Folgendes verwendet: schreibgeschützt (0), schreibgeschützt (1) und schreibgeschützt (2).
Lesen aus einer Datei
Führen Sie zum Lesen aus einer Datei die folgenden Aufgaben aus:
Fügen Sie den Systemaufruf sys_read () Nummer 3 in das EAX-Register ein.
Legen Sie den Dateideskriptor in das EBX-Register.
Setzen Sie den Zeiger auf den Eingangspuffer im ECX-Register.
Tragen Sie die Puffergröße, dh die Anzahl der zu lesenden Bytes, in das EDX-Register ein.
Der Systemaufruf gibt die Anzahl der im EAX-Register gelesenen Bytes zurück. Im Fehlerfall befindet sich der Fehlercode im EAX-Register.
In eine Datei schreiben
Führen Sie zum Schreiben in eine Datei die folgenden Aufgaben aus:
Fügen Sie den Systemaufruf sys_write () Nummer 4 in das EAX-Register ein.
Legen Sie den Dateideskriptor in das EBX-Register.
Setzen Sie den Zeiger auf den Ausgabepuffer im ECX-Register.
Tragen Sie die Puffergröße, dh die Anzahl der zu schreibenden Bytes, in das EDX-Register ein.
Der Systemaufruf gibt die tatsächliche Anzahl der in das EAX-Register geschriebenen Bytes zurück. Im Fehlerfall befindet sich der Fehlercode im EAX-Register.
Eine Datei schließen
Führen Sie zum Schließen einer Datei die folgenden Aufgaben aus:
- Fügen Sie den Systemaufruf sys_close () Nummer 6 in das EAX-Register ein.
- Legen Sie den Dateideskriptor in das EBX-Register.
Der Systemaufruf gibt im Fehlerfall den Fehlercode im EAX-Register zurück.
Aktualisieren einer Datei
Führen Sie zum Aktualisieren einer Datei die folgenden Aufgaben aus:
- Tragen Sie den Systemaufruf sys_lseek () Nummer 19 in das EAX-Register ein.
- Legen Sie den Dateideskriptor in das EBX-Register.
- Tragen Sie den Offset-Wert in das ECX-Register ein.
- Tragen Sie die Referenzposition für den Offset in das EDX-Register ein.
Die Referenzposition könnte sein:
- Dateianfang - Wert 0
- Aktuelle Position - Wert 1
- Dateiende - Wert 2
Der Systemaufruf gibt im Fehlerfall den Fehlercode im EAX-Register zurück.
Beispiel
Das folgende Programm erstellt und öffnet eine Datei mit dem Namen myfile.txt und schreibt einen Text 'Welcome to Tutorials Point' in diese Datei. Als nächstes liest das Programm aus der Datei und speichert die Daten in einem Puffer namens info . Zuletzt wird der in info gespeicherte Text angezeigt .
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
;create the file
mov eax, 8
mov ebx, file_name
mov ecx, 0777 ;read, write and execute by all
int 0x80 ;call kernel
mov [fd_out], eax
; write into the file
mov edx,len ;number of bytes
mov ecx, msg ;message to write
mov ebx, [fd_out] ;file descriptor
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
; close the file
mov eax, 6
mov ebx, [fd_out]
; write the message indicating end of file write
mov eax, 4
mov ebx, 1
mov ecx, msg_done
mov edx, len_done
int 0x80
;open the file for reading
mov eax, 5
mov ebx, file_name
mov ecx, 0 ;for read only access
mov edx, 0777 ;read, write and execute by all
int 0x80
mov [fd_in], eax
;read from file
mov eax, 3
mov ebx, [fd_in]
mov ecx, info
mov edx, 26
int 0x80
; close the file
mov eax, 6
mov ebx, [fd_in]
int 0x80
; print the info
mov eax, 4
mov ebx, 1
mov ecx, info
mov edx, 26
int 0x80
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
file_name db 'myfile.txt'
msg db 'Welcome to Tutorials Point'
len equ $-msg
msg_done db 'Written to file', 0xa
len_done equ $-msg_done
section .bss
fd_out resb 1
fd_in resb 1
info resb 26
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
Written to file
Welcome to Tutorials Point
Das sys_brk()Der Systemaufruf wird vom Kernel bereitgestellt, um Speicher zuzuweisen, ohne ihn später verschieben zu müssen. Dieser Aufruf weist Speicher direkt hinter dem Anwendungsabbild im Speicher zu. Mit dieser Systemfunktion können Sie die höchste verfügbare Adresse im Datenbereich einstellen.
Dieser Systemaufruf verwendet einen Parameter, der die höchste Speicheradresse ist, die eingestellt werden muss. Dieser Wert wird im EBX-Register gespeichert.
Im Fehlerfall gibt sys_brk () -1 oder den negativen Fehlercode selbst zurück. Das folgende Beispiel zeigt die dynamische Speicherzuordnung.
Beispiel
Das folgende Programm reserviert 16 KB Speicher mithilfe des Systemaufrufs sys_brk () -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, 45 ;sys_brk
xor ebx, ebx
int 80h
add eax, 16384 ;number of bytes to be reserved
mov ebx, eax
mov eax, 45 ;sys_brk
int 80h
cmp eax, 0
jl exit ;exit, if error
mov edi, eax ;EDI = highest available address
sub edi, 4 ;pointing to the last DWORD
mov ecx, 4096 ;number of DWORDs allocated
xor eax, eax ;clear eax
std ;backward
rep stosd ;repete for entire allocated area
cld ;put DF flag to normal state
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 80h ;print a message
exit:
mov eax, 1
xor ebx, ebx
int 80h
section .data
msg db "Allocated 16 kb of memory!", 10
len equ $ - msg
Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:
Allocated 16 kb of memory!