Technische Informatik I
x86 Maschinensprache + Assembler
        
        
          Thorsten Thormählen 
 14. Januar 2025 
 Teil 10, Kapitel 1
        
        
        
      
      
      
        
          
Steuerungstasten
        
        
          → nächste Folie (auch 
Enter oder 
Spacebar).
          
← vorherige Folie
          
 d  schaltet das Zeichnen auf Folien ein/aus
          
 p  wechselt zwischen Druck- und Präsentationsansicht
          
CTRL  +  vergrößert die Folien
          
CTRL  -  verkleinert die Folien
          
CTRL  0  setzt die Größenänderung zurück
          
 
            Das Weiterschalten der Folien kann ebenfalls durch das Klicken auf den rechten bzw. linken Folienrand erfolgen.
           
         
        
      
    
    
      
        
Notation
      
      
        
          
            | Typ | 
            Schriftart | 
            Beispiele | 
          
          
            | Variablen (Skalare) | 
            kursiv | 
            $a, b, x, y$ | 
          
          
            | Funktionen | 
            aufrecht | 
            $\mathrm{f}, \mathrm{g}(x), \mathrm{max}(x)$ | 
          
          
            | Vektoren | 
            fett, Elemente zeilenweise | 
            $\mathbf{a}, \mathbf{b}= \begin{pmatrix}x\\y\end{pmatrix} = (x, y)^\top,$ $\mathbf{B}=(x, y, z)^\top$ | 
          
          
            | Matrizen | 
            Schreibmaschine | 
            $\mathtt{A}, \mathtt{B}= \begin{bmatrix}a & b\\c & d\end{bmatrix}$ | 
          
          
            | Mengen | 
            kalligrafisch | 
            $\mathcal{A}, B=\{a, b\}, b \in \mathcal{B}$ | 
          
          
            | Zahlenbereiche, Koordinatenräume | 
            doppelt gestrichen | 
            $\mathbb{N}, \mathbb{Z}, \mathbb{R}^2, \mathbb{R}^3$ | 
          
        
       
    
    
    
      
        
Inhalt
      
      
        
        
          - Einführung in die x86 Maschinen- und Assemblersprache
 
          - Register der x86 Familie
 
          - Assemblerbefehle der x86 Familie
 
        
       
    
    
    
      
        
Maschinensprache
      
      
        
          - Computer werden durch Programme gesteuert, die aus Befehlen in Maschinensprache bestehen (wie aus dem vorangegangenen Kapitel bekannt) 
 
          - Diese Befehle liegen im Hauptspeicher und werden nacheinander abgearbeitet
 
          - Die Maschinensprache wird vom Hersteller einer CPU definiert und kann vom Programmierer nicht verändert werden
 
          - Der Hersteller legt fest, welcher Zahlencode im Speicher welchem Maschinensprachebefehl entspricht
                  
          
          
 
          - Damit ist ein Programm in Maschinensprache immer nur auf einer bestimmten CPU bzw. auf
            den untereinander kompatiblen CPUs einer Prozessorfamilie lauffähig
   
        
       
    
    
    
      
        
Maschinensprache / Assembler
      
      
        - Eine Maschinensprache ist die Menge von Befehlen, die einem Programmierer für eine konkrete CPU
            zur Verfügung steht
 
          - Diese Befehle werden normalerweise in der Praxis nicht direkt durch ihren Zahlencode eingegeben, sondern in
            einer Assemblersprache (auch "Assembler" genannt)
 
          - Eine Assemblersprache ist einer lesbare Art, Programme in Maschinensprache zu formulieren
 
          - Die lesbare Variante eines Maschinenbefehls in Assemblersprache wird Mnemonik genannt 
 
          - So wird z.B. für den Sprungbefehl die Mnemonik 
jmp verwendet (für engl. "jump"); 
          - Es gibt somit eine eineindeutige Abbildung zwischen Maschinensprache und Assemblersprache
            
              - Ein Assemblierer ist ein Übersetzer von Assemblersprache in Maschinensprache
 
              - Ein Disassemblierer ist ein Übersetzer von Maschinensprache in Assemblersprache
 
            
           
        
       
    
    
    
      
        
Erzeugung von Maschinensprache mittels Compiler
      
      
        
        
          - Softwareentwicklung findet heutzutage typischerweise in einer höheren Programmiersprache statt (z.B. C, C++, Fortran, Pascal, ...)
          
 - Um ein in einer höheren Programmiersprache geschriebenes Programm ausführen zu können, muss dies 
            jedoch letztendlich durch einen Compiler in eine äquivalente Folge von Maschinenbefehlen übersetzt werden
 
          - Beim Kompilieren kann angegeben werden für welchen CPU Typ der Maschinencode erzeugt werden soll
 
          - Daher kann ein Programm in Hochsprache im Gegensatz zu einem Assemblerprogramm 
            leicht auf andere Hardware angepasst werden
 
        
       
    
    
    
    
      
        
Inline-Assembler
      
      
        
        
          - Früher wurden viele Programme in Assembler erstellt
 
          - Heute ist mit der schnelleren Hardware die Bedeutung von Maschinensprache zurückgedrängt worden
 
          - Softwareentwicklung findet typischerweise in einer höheren Programmiersprache statt
 
          - Bei sehr zeitkritischen Funktionen innerhalb eines Programms wird jedoch weiterhin Maschinensprache in
          Form von Inline-Assembler verwendet
 
          - Der Compiler optimiert nur den Teil, der in der Hochsprache geschrieben ist, der Assemblerteil wird direkt in
            die Maschinensprache übertragen
 
        
       
    
    
    
    
      
        
x86 Maschinensprache
      
      
        
          - In dieser Vorlesung wird aufgrund der starken Verbreitung und Praxisrelevanz, im Folgenden die Maschinensprache der
            x86 Prozessorfamilie vorgestellt
 
          - Diese Maschinensprache ist auf allen 80x86 und x86-64 Prozessoren der Firmen Intel und AMD ausführbar, 
            d.h. auf fast jedem Desktop-PC oder Laptop (Ausnahmen sind z.B. PowerPC-basierte Apple Computer)
 
          - Hörer der Vorlesung können zur Maschinensprache-Programmierung einen Rechner in den PC-Pools des Fachbereichs oder ihren privaten Rechner verwenden
 
        
       
    
    
    
    
      
        
Werkzeuge zur Erstellung von x86 Maschinensprache
      
      
        
          - Zur Erstellung vom x86 Assembler stehen verschiedene Werkzeuge zur Auswahl:
            
        
              - Microsoft Macro Assembler (MASM) ist ein
                 verbreiteter Assembler für MS-DOS und Windows
 
              - MASM32 ist eine freie Variante von MASM
 
              - Turbo Assembler (TASM) ist ein Assembler von Borland für das Betriebssystem MS-DOS
 
              - NASM
                ("Netwide Assembler") ist ein beliebter Assembler, der für verschiedene Betriebssysteme (DOS, Windows, Unix) verwendet werden kann. 
                Es gibt sogar Online-Dienste bei denen ohne Installation auf dem eigenen Rechner mit NASM experimentiert werden kann: 
                https://www.jdoodle.com,
                https://onecompiler.com,
                
               
            
           
          - Diese Werkzeuge unterscheiden sich natürlich nicht in den Maschinenbefehlen (diese sind durch die x86 Prozessorfamilie vorgegeben) sondern
            lediglich in der Art der Darstellung und durch unterschiedliche Makros, die die Arbeit erleichtern sollen
          
 
        
       
    
    
    
      
        
Inline-Assembler in Microsoft Visual C/C++
      
      
        
          - In dieser Vorlesung werden die gezeigten Maschinensprache-Beispiele in Form von Inline-Assembler innerhalb eines C/C++-Programms 
            vorgestellt 
 
          - Als Entwicklungsumgebung wird Microsoft Visual Studio verwendet
          
 - Vorteile:
            
            - Inline-Assembler wird in der Praxis gerne verwendet, da niemand größere Projekte komplett in Assembler entwickeln möchte
 
            - Auf Symbole und Variablen des C/C++-Programms kann direkt innerhalb des Assembler-Codes zugegriffen werden
 
            - Die Syntax ist verhältnismäßig einfach
 
            - Microsoft Visual Studio hat einen integrierten Debugger
 
            - Auch Registerinhalte, Speicher etc. lassen sich komfortabel anzeigen
 
            - Visual Studio ist auf jedem Windows-Rechner im Fachbereich installiert
 
            - Microsoft Visual Studio (Community Version) steht kostenfrei zur Verfügung:  
               Download
 
            
           
          - Nachteil:
            
              - Plattformabhängigkeit: Für die Verwendung mit anderen Assemblern muss die Syntax entsprechend angepasst werden
 
            
           
        
       
    
   
    
    
      
        
Inline-Assembler in Microsoft Visual C/C++
      
      
        
#include <stdio.h> // includes "printf" command ;
int add(int varA, int varB ) {
   // inline assembler starts here
   __asm {
      mov eax, varA   ; // move first argument to EAX register
      mov ecx, varB   ; // move second argument to ECX register
      add eax, ecx    ; // EAX = EAX + ECX
   }
}
int main() {
  int result = add(1, 2);    // call C-function "add"
  printf( "1 + 2 = %d\n", result); // output result to console
  return 0;
}
        
          - Quelldatei: main.cpp 
          
 - Projekt für MS Visual Studio: 
            
            
            VS2012,
            VS2013
            VS2015
            VS2017
            VS2019
            VS2022
          
 - Diese Projektdateien können für alle folgenden Beispiele wiederverwendet werden, indem jeweils die Quelldatei "main.cpp" ersetzt wird
 
        
       
    
    
    
    
      
        
Inline-Assembler in Microsoft Visual C/C++
      
      
        
        
          - Verwendung vom Microsoft Visual Studio:
            
              - Erstellen des Projekts: F7
 
              - Ausführen: CTRL+F5
 
              - Debuggen starten: F5
 
              - Debuggen Einzelschritt: F11
 
              - Debuggen Prozedurschritt: F10
 
              - Breakpoint setzen/entfernen: in die Leiste neben dem Quellcode klicken
 
              - Anzeigen von Registern:
 Menü → Debuggen→ Fenster→ Register 
              - Anzeigen von Arbeitsspeicher: 
Menü→ Debuggen→ Fenster→ Arbeitsspeicher 
            
           
        
       
    
    
    
    
      
        
Alternative für Linux-Nutzer
      
      
        
          - Unter Linux ist die Verwendung von Microsoft Visual Studio nicht möglich. Als Alternative zum Kompilieren der bereitgestellen Beispiele 
            kann der Intel C and C++ Compiler mit der Option 
-use-msasm verwendet werden
            
              In einer Shell:
              > icc -use-msasm -o my_program main.cpp 
             
           
          - Der Intel Compiler steht Studenten kostenfrei zur Verfügung: Download
          
 - Eine weitere Möglichkeit sind Online-Dienste, bei denen ohne Installation auf dem eigenen Rechner mit dem Microsoft 
              Compiler experimentiert werden kann:
            
          
 
          - Diese Alternativen haben jedoch keinen integrierten Debugger und werden daher nur bedingt empfohlen
 
          - Microsoft Visual Studio ist auf jedem Windows-Rechner im Fachbereich installiert
 
        
       
    
    
    
    
      
        
Register der x86-Familie
      
      
        
    
    
      
        
Register der x86-Familie
      
      
        
          - Die ersten x86-Prozessoren (8088, 8086 und 80286) waren 16-Bit Prozessoren
 
          - Sie hatten 14 Register (orange gekennzeichnet in Abb.)
            
              - 8 Allzweckregister, die jedoch jeweils auch noch eine Sonderfunktion hatten
 
              - 4 Segment Register
 
              - 1 Befehlszähler
 
              - 1 Flagregister
 
            
           
          - Das niederwertige Byte (Low Byte) bzw. höherwertige Byte (High Byte) 
            der Register 
AX, BX, CX und DX sind als 8-Bit Register
           gesondert ansprechbar (z.B. AX besteht aus AL und AH) 
          
          
          - Seit dem 80386 wird mit 32-Bit breiten Registern gearbeitet
 
          - Die bestehenden Register wurden erweitert (gelb gekennzeichnet) und können als
          
EAX, EBX, usw. angesprochen werden 
          
          - Die 16-Bit Teile der Register sind weiterhin ansprechbar. Neuerungen in der x86-Architektur waren bisher
          immer abwärts-kompatibel, d.h. neuere Prozessoren können ältere Programme immer noch ausführen
 
          
          - Spätere x86-Prozessoren haben weitere Register eingeführt (MMX-Register, SSD-Register, usw.)
            diese werden zunächst hier nicht weiter betrachtet
        
 
       
      
    
    
    
      
        
Allzweckregister
      
      
        
          - Die Allzweckregister haben jeweils noch eine Sonderfunktion:
            
              | Register | Sonderfunktion |  Erklärung | 
              AX | Akkumulator |  Ziel für Rechenoperationen | 
              BX |  Base |   Zeiger für Zugriffe auf Speicher | 
              CX  | Counter |   Zähler in Schleifen | 
              DX  | Data |  Datenregister | 
              SI |  Source Index |   Quellindex für Verarbeitung von Zeichenketten | 
              DI |  Destination Index |   Zielindex für Verarbeitung von Zeichenketten | 
              BP |  Base Pointer |   Anfangsadresse des Stapelsegments | 
              SP |  Stack Pointer |   Stapelzeiger | 
            
           
        
       
        
    
    
    
      
        
Flagregister
      
      
        
         
         - Das Flagregister ändert sich nach arithmetischen Operationen
 
          - Es dient dazu, die Situationen nach der Durchführung einer ALU-Operation anzuzeigen
 
          - Es ist eigentlich ein Ausgaberegister, dennoch kann es auf dem Umweg über den später zu besprechenden Stack gezielt verändert werden
 
          - Von den 16 bzw. 32 Bits des Flag-Registers sind nur 8 für uns interessant:
            
              - Die Flags CY, AC, OV, PL, ZR, PE beziehen sich immer auf das Ergebnis einer gerade durchgeführten Operation
 
              - Die Flags UP, EI  dienen als Schalter. Sie bleiben unverändert, bis man sie durch Spezialbefehle verändert
 
            
         
       
    
   
    
    
      
        
Flagregister
      
      
        
         
          | Flag | Bedeutung |   Erklärung | 
          | CY | Carry |   	Bereichsüberschreitung für vorzeichenlose Zahlen | 
          | AC | Aux. Carry |   Bereichsüberschreitung für vorzeichenlose 4-Bit Zahlen  | 
          | OV | Overflow |   Bereichsüberschreitung bei arithmetischer Operation auf Zahlen mit Vorzeichen | 
          | PL | Sign |   Ergebnis war negativ | 
          | ZR | Zero |   Ergebnis war null | 
          | PE | Parity |   Ergebnis hat eine gerade Anzahl von Einsen  (im niederwertigsten Byte) | 
          | UP | Direction |   Legt die Richtung von String-Befehlen fest | 
          | EI | Interrupt |   Bestimmt, ob Interrupts zugelassen werden | 
        
       
    
    
    
    
      
        
Segmentregister
      
      
        
          - Die Segmentregister werden später bei der Adressierung des Speichers
            eine Rolle spielen:
            
              | Register | Funktion |   Erklärung | 
              CS | Code Segment |   Anfangsadresse des Code-Segments | 
              DS | Data Segment |   Anfangsadresse des Daten-Segments | 
              SS | Stack Segment |  Anfangsadresse des Stack-Segments | 
              ES | Extra Segment |  keine besondere Funktion | 
   
              FS |  | keine besondere Funktion  | 
              GS |  | keine besondere Funktion  | 
            
           
        
       
    
    
    
    
    
      
        
Format von Assemblerbefehlen
      
      
        
           - In Assembler wird je ein Befehl pro Zeile geschrieben
 
          - Dabei hat ein Assemblerbefehl die Form:
            
              [Label] [Operation] [Operanden] [;Kommentar]
             
           
          - Die eckigen Klammern sollen andeuten, dass diese Komponente optional ist, d.h.
            es ist auch eine leere Zeile möglich
 
          - Zwischen Groß- und Kleinschreibung wird nicht unterschieden (außer wenn im Inline-Assembler 
            auf C/C++ Variablen zugegriffen wird)
 
          - Leerzeichen zwischen den Komponenten werden ignoriert
 
          - Bei einem Semikolon beginnt ein Kommentar, der sich bis zum Zeilenende erstreckt
 
          - Alternativ kann ein Kommentar in MS Visual Studio auch mit den in C/C++ üblichen doppelten Schrägstrichen "//" gekennzeichnet werden
 
        
       
       
    
    
     
    
      
        
Format von Assemblerbefehlen
      
      
          
      
     
    
      
        
Der Assemblerbefehl MOV
      
      
        
          - Einer der meist verwendeten Befehle ist der Move-Befehl:
                        
          
 - Dabei werden die Daten aus der Quelle zum Ziel kopiert
 
          - Ziel kann ein Register oder eine Speicheradresse sein
 
          - Quelle kann ein Register, eine Speicheradresse oder eine Kontante sein
 
          - Einschränkungen:
            
              - Es können nicht beide Operanden Speicheradressen sein
 
              - Es können nicht beide Operanden Segmentregister sein
 
              - Konstanten können nicht direkt in Segmentregister geschrieben werden
 
            
           
        
       
          
   
     
    
      
        
Der Assemblerbefehl MOV
      
      
        
          - Beispiele für den Move-Befehl:
int main() {
  int varA = 6;
  int varB = 7;
  __asm {
    mov eax, 10h        ; // move the hex value 10 to EAX
    mov eax, 10         ; // move the decimal value 10 to EAX
    mov eax, 010        ; // move the octal value 10 to EAX
    mov ecx, eax        ; // move EAX register to ECX register
    //mov  fs, ds       ; // does not work
    //mov  ax,  ds      ; // this works instead: (but segment registers
    //mov  fs,  ax      ; // should not be modified)
    mov eax, 11111111h  ; // EAX = 11111111h;
    mov ax, 3333h       ; // EAX = 11113333h;
    //mov  varA, varB   ; // does not work
    mov  eax, varB      ; // this works instead:
    mov  varA, eax     
  }
  return 0;
}
 
           
          - Quelldatei: main.cpp 
        
 
       
      
     
     
    
      
        
Der Assemblerbefehl ADD
      
      
        
          - Der Befehl 
add führt eine Addition aus:
            
           - Beispiele für den Add-Befehl (Quelldatei: main.cpp):
            
#include <stdio.h>
int main() {
  int varA = 6, varB = 7;
  __asm {
    //add varA, varB      ; // does not work
    mov eax, varB         ; // this works instead:
    add varA, eax         ; //
  }
  printf("varA = %d\n", varA);
  __asm {
    mov eax, 21         
    mov edx, 24
    add eax, edx
    mov varA, eax
  }
  printf("varA = %d\n", varA);
  return 0;
} 
             
           
        
       
      
   
     
    
      
        
Arithmetische Operationen und das Flagregister
      
      
        
        
          - Arithmetische Operationen, wie z.B. der Befehl 
add, verändern das Flagregister 
          - Der Prozessor weiß nicht, ob der Inhalte eines Registers als vorzeichenlose Zahl (unsigned) 
            oder als vorzeichenbehaftete ganze Zahl in Zweierkomplement-Darstellung interpretiert werden muss
 
          - Die Operation wird einfach ausgeführt und die Flags gesetzt. Die Interpretation der Flags ist
            Sache des Programmierers
 
          - Nicht alle Befehle beeinflussen alle Flags, so dass auch später noch durch eine frühere Operation
            erzeugte Flags ablesbar sind
          
 
          - Der Befehl 
add beeinflusst: OV,PL,ZR,AC,
            PE und CY 
        
       
          
     
    
      
        
Arithmetische Operationen und das Flagregister
      
      
        
        
          - Beispiele (Quelldatei: main.cpp):
        
int main() {
  __asm
  {
    mov eax, 00000000h    
    mov ecx, 00000000h   
    add eax, ecx    ; // EAX=0;        Flags: OV=0 PL=0 ZR=1 AC=0 PE=1 CY=0 
    mov eax, 00000000h    
    mov ecx, 00000001h   
    add eax, ecx    ; // EAX=1;        Flags: OV=0 PL=0 ZR=0 AC=0 PE=0 CY=0
    mov eax, 00000001h    
    mov ecx, 0000000Fh   
    add eax, ecx    ; // EAX=10;       Flags: OV=0 PL=0 ZR=0 AC=1 PE=0 CY=0
    mov eax, 00000000h    
    mov ecx, 7FFFFFFFh   
    add eax, ecx    ; // EAX=7FFFFFFF; Flags: OV=0 PL=0 ZR=0 AC=0 PE=1 CY=0
    mov eax, 00000001h    
    mov ecx, 7FFFFFFFh    
    add eax, ecx    ; // EAX=80000000; Flags: OV=1 PL=1 ZR=0 AC=1 PE=1 CY=0
    mov eax, 00000001h    
    mov ecx, -2     ; // ECX=FFFFFFFE; (Two's complement)  
    add eax, ecx    ; // EAX=FFFFFFFF; Flags: OV=0 PL=1 ZR=0 AC=0 PE=1 CY=0
    mov eax, 00000001h  
    mov ecx, -1     ; // ECX=FFFFFFFF; 
    add eax, ecx    ; // EAX=00000000; Flags: OV=0 PL=0 ZR=1 AC=1 PE=1 CY=1
  }
  return 0;
} 
             
           
        
       
       
     
      
      
        
          
Quiz
        
        
       
      
     
     
       
         
Arithmetische Operationen und das Flagregister
       
            
         
           
    mov al, -3   ; // AL=FDh; 
    mov cl, -1   ; // CL=FFh; 
    add al, cl   ; // AL=FCh; Flags: OV=0 PL=1 ZR=0 AC=1 PE=1 CY=1 
    mov al, 253  ; // AL=FDh;  
    mov cl, 255  ; // CL=FFh; 
    add al, cl   ; // AL=FCh; Flags: OV=0 PL=1 ZR=0 AC=1 PE=1 CY=1 
          
         
           - 
           In diesem Beispiel (Quelldatei: main.cpp)
            werden zweimal exakt die gleichen Operationen ausgeführt
 
           - Der Assembler übersetzt -3 und 253 in das gleiche Bitmuster: 11111101 = FDh 
 
           - Der Assembler übersetzt -1 und 255 in das gleiche Bitmuster: 11111111 = FFh 
 
           - Nach der Addition ist in beiden Fällen das Ergebnis FCh=252 und Overflow=0 Carry=1
 
           - Interpretation als Addition von vorzeichenlosen Zahlen:
             
              - Das gesetzte Carry zeigt, dass das Ergebnis 252 falsch ist: 253+255=508
 
             
            
            - Interpretation als Addition von vorzeichenbehafteten Zahlen im Zweierkomplement:
             
              - Das nicht gesetzte Overflow zeigt, dass das Ergebnis FCh=-4 richtig ist: -3 + -1 =-4
 
             
            
           - Erkenntnis: Für die richtige Interpretation sind die Flags wichtig
         
 
        
       
     
     
     
       
         
Der Assemblerbefehl ADC
       
            
           
             - Um die Addition 253+255=508 doch richtig auszuführen, gibt es zwei Möglichkeiten
               
                 - Ein größeres Register verwenden
mov eax, 253  
mov ecx, 255  
add eax, ecx   ; // EAX=000001FC; Flags: OV=0 PL=0 ZR=0 AC=1 PE=1 CY=0
                
                  
                 - Den Befehl 
adc verwenden, der genau wie der add Befehl arbeitet, jedoch
                   zusätzlich das Carry-Bit hinzuaddiert
mov al, 253    ; // AL=FDh;  
mov cl, 255    ; // CL=FFh;
mov ah, 0      ; // AH=0h;
add al, cl     ; // AL=FCh; AH=0h; Flags: OV=0 PL=1 ZR=0 AC=1 PE=1 CY=1
adc ah, 0      ; // AL=FCh; AH=1h; Flags: OV=0 PL=0 ZR=0 AC=0 PE=0 CY=0
// AX=01FCh 
         
                
             
              
             
           
        
       
     
     
     
    
      
        
Der Assemblerbefehl SUB
      
      
        
          - Der Befehl 
sub führt eine Subtraktion aus:
            
           - Der Befehl beeinflusst die gleichen Register, wie der Add-Befehl: 
OV,PL,ZR,AC,
            PE und CY 
          - Bei x86 wird das Carry 
CY gesetzt, wenn $($ziel $<$ quelle$)$ (bei Interpretation als vorzeichenlose Zahl).
              Das Carry erfüllt dann die Funktion eines Borrow.
           
          - 
            Bei vorzeichenbehafteten Zahlen ist wieder nur das Overflow-Flag 
OV relevant
           
          
          - Eine Subtraktion kann hardwaretechnische durch eine Addtionen von 
quelle und dem Zweierkomplement von ziel implementiert werden,
            also $a - b = a + (-b)$ (siehe Kapitel Arithmetik Schaltungen )
            
          - Dies funktioniert sofort perfekt bei Interpretation der Operanden als vorzeichenbehaftete Zahl
 
          - Bei der Interpretation als vorzeichenlose Zahl wird allerdings das Carry von der gezeigten Harwareschaltung gesetzt, wenn $($
ziel $\ge$ quelle$)$.
            Für den x86-sub Befehl, müsst das Carry der Harwareschaltung demnach invertiert werden 
            
           
        
       
      
     
     
    
      
        
Der Assemblerbefehl SUB
      
      
        
          - Beispiel für den sub-Befehl (Quelldatei: main.cpp):
            
int main() {
  __asm {
    mov al, 2      ; // AL=02h;
    mov bl, -1     ; // BL=FFh;
    sub al, bl     ; // AL=03h; Flags: OV=0 PL=0 ZR=0 AC=1 PE=1 CY=1
    mov al, 2      ; // AL=02h;
    sub al, 255    ; // AL=03h; Flags: OV=0 PL=0 ZR=0 AC=1 PE=1 CY=1
  }
  return 0;
}
 
             
           
          - Kontrollrechnung mit dem Zweierkomplement (Carry 
CY wird invertiert): 
            
            \begin{aligned}
             00000010&&(2)\\
           - 11111111&&(-1) \,\,  \mbox{bzw.}  \,\,  (255)\\ 
          {\scriptsize\downarrow}  \quad \quad \\
           00000010&&(2)\\
           + 00000001&&  \mbox{Zweierkomplement}\\ \hline
           00000011&& (3)
            \end{aligned}
            
           
          
          
        
       
      
     
     
    
      
        
Der Assemblerbefehl SBB
      
      
        
          - Der Befehl 
sbb steht für engl. "subtract with borrow", d.h.
            es wird eine Subtraktion ausgeführt und zusätzlich das Carry subtrahiert 
          - Beispiel für den sbb-Befehl (Quelldatei: main.cpp):
            
int main() {
  __asm {
    // 
    mov ax, 800    ; // AX=320h; AL=20h
    mov cl, 34     ; // CL=22h;
    sub al, cl     ; // AL=FEh=-2; Flags: OV=0 PL=1 ZR=0 AC=1 PE=0 CY=1
    sbb ah, 0      ; // AH=02h;    Flags: OV=0 PL=0 ZR=0 AC=0 PE=0 CY=0
    // AX=2FE=766
  }
  return 0;
}
 
             
           
        
       
      
     
     
     
       
         
Größenvergleich mittels SUB Befehl
       
       
         
           - Der Befehl 
sub kann verwendet werden, um zwei Operanden zu vergleichen 
           - 
             
           
 
           - Ist es so einfach?
             
               - Zero-Flag=0, Sign-Flag=1 bedeutet operandA < operandB
 
               - Zero-Flag=1, Sign-Flag=0 bedeutet operandA = operandB
 
               - Zero-Flag=0, Sign-Flag=0 bedeutet operandA > operandB
 
             
            
         
         
           - Nein, es kommt darauf an, ob es sich um eine vorzeichenlose oder vorzeichenbehaftete
             Zahl handelt. Beispiel:
             
               - Ist der Hex-Wert 2h kleiner oder großer als FFh?
 
               - Wenn vorzeichenlos: 2h=2, FFh=255 → Ergebnis: kleiner
 
               - Wenn vorzeichenbehaftet: 2h=2, FFh=-1 → Ergebnis: größer
 
             
            
           - Daher werden zwei verschiedene Größenvergleiche eingeführt:
             
               - Wenn vorzeichenlos: "above" und "below" 
 
               - Wenn vorzeichenbehaftet: "greater" und "less"
 
             
          
        
       
     
     
     
       
         
Größenvergleich mittels SUB Befehl
       
       
         
         
           - Vorzeichenlose Zahlen: "above" und "below" 
           
             | Aussage | Bedingung | 
             | operandA below operandB | CY=1 | 
             | operandA equal operandB | ZR=1 | 
             | operandA above operandB | CY=0 | 
           
            
           - Vorzeichenbehaftete Zahlen: "less" und "greater" 
           
             | Aussage | Bedingung | 
             | operandA less operandB | PL ≠ OV | 
             | operandA equal operandB | ZR=1 | 
             | operandA greater operandB | PL = OV | 
           
            
         
        
      
     
     
     
       
         
Größenvergleich mittels SUB Befehl
       
       
         
         
           - Vorzeichenlose Zahlen: "above" und "below" 
           
             | Aussage | Bedingung | 
             | operandA below operandB | CY=1 | 
             | operandA equal operandB | ZR=1 | 
             | operandA above operandB | CY=0 | 
           
            
           - Erklärung:
             
               - Beim 
sub Befehl wird das Carry als Borrow verwendet 
               - Ein Borrow wird nur benötigt, wenn 
operandA kleiner als operandB ist 
             
            
         
        
      
     
     
     
       
         
Größenvergleich mittels SUB Befehl
       
       
         
         
           - Vorzeichenbehaftete Zahlen: "less" und "greater" 
           
             | Aussage | Bedingung | 
             | operandA less operandB | PL ≠ OV | 
             | operandA equal operandB | ZR=1 | 
             | operandA greater operandB | PL = OV | 
           
            
           - Zur Erklärung betrachten wir den Fall, dass 
operandA größer als operandB ist:
             
               
               - PL = OV = 0, kein Overflow ist aufgetreten (d.h. keine unerwarteter Vorzeichenwechsel, Ergebnis stimmt) und Ergebnis ist positiv
 
               - PL = OV = 1, Overflow ist aufgetreten (d.h. unerwarteter Vorzeichenwechsel, Ergebnis falsch) und das Ergebnis ist negativ ("negativer Overflow").
                 Der negative Overflow bedeutet, dass das Ergebnis eigentlich positiv sein sollte und daher operandA größer als operandB ist.
                
               - Obwohl das Berechnungsergebnis falsch ist (angezeigt durch den Overflow), kann trotzdem ein Größenvergleich durchgeführt werden
 
             
            
         
        
       
     
      
    
      
        
Größenvergleich mittels SUB Befehl
      
      
        
          - Beispiel für den Größenvergleich von vorzeichenbehafteten Zahlen:
            
void main() {
  __asm {
    mov eax, -1
    mov ecx, -2
    sub eax, ecx         ; // OV = 0, PL = 0, ZR = 0 -> eax > ecx
    mov eax, -2
    mov ecx, -1
    sub eax, ecx         ; // OV = 0, PL = 1, ZR = 0 -> eax < ecx
    mov eax, 7FFFFFFFh   ; // largest positive signed integer
    mov ecx, -1
    sub eax, ecx         ; // OV = 1, PL = 1, ZR = 0 -> eax > ecx
    mov eax, 80000000h   ; // largest negative signed integer
    mov ecx, 1
    sub eax, ecx         ; // OV = 1, PL = 0, ZR = 0 -> eax < ecx
    mov eax, 1           
    mov ecx, 1
    sub eax, ecx         ; // OV = 0, PL = 0, ZR = 1 -> eax == ecx
  }
}
 
              Quelldatei: 
main.cpp
             
           
        
       
       
      
     
     
       
         
Der Assemblerbefehl CMP
       
       
         
           - Da für einen Vergleich nur die Flags nach der Subtraktion eine 
             Rolle spielen, nicht aber das Ergebnis, gibt es folgenden Maschinenbefehl:
             
           
 - Dieser Befehl setzt genau die Flags, die ein analoger Sub-Befehl setzen würde
 
           - Der wesentliche Unterschied ist, dass der operandA unverändert bleibt
 
           - Die Auswertung der gesetzten Flags erfolgt meist durch einen 
             direkt nachfolgenden bedingten Sprung-Befehl.
 
         
        
       
     
     
     
       
         
Sprungbefehle und Sprungmarken
       
       
         
           - Assemblerbefehle werden in der Reihenfolge ausgeführt, 
             in der sie im Text erscheinen, es sei denn, es handelt sich
             um einen Sprungbefehl
 
           - Ein solcher bewirkt die Fortsetzung des Programms an einer beliebigen anderen Stelle
 
           - Das Argument eines Sprungbefehls ist das Ziel des Sprungs, das über eine Adresse oder eine Sprungmarke (engl. "label") 
             angegeben wird
 
           - Die Sprungmarke wird vom Assembler in eine Adresse umgesetzt
 
           - Eine Sprungmarke, die als Sprungziel dienen soll, muss mit einem Doppelpunkt abgeschlossen werden
 
           - Als Sprungmarke dürfen natürlich keine Wörter verwendet werden, die bereits in der Assemblersprache anderweitig genutzt werden
 
           - Die Sprungbefehle realisieren einen Sprung, in dem der Befehlszähler 
EIP auf die Zieladresse gesetzt wird 
           
         
        
       
     
    
     
       
         
Der Assemblerbefehl JMP
       
       
         
           - Für den unbedingten Sprung gibt es den JMP-Befehl (engl. "jump")
             
           
 - Sprungziel kann eine Sprungmarke oder eine Adresse sein 
 
           - Beispiel:
int main() {
  __asm {
             mov eax, 3      
             jmp Important 
             mov eax, 33
  Important: mov ecx, 6
             add eax, ecx  ; // eax = ?
  }
  return 0;
}   Quelldatei: main.cpp
            
          
        
       
     
    
     
       
         
Assemblerbefehle für bedingte Sprünge
       
       
         
           - Die bedingten Sprünge werten die Flags aus, die durch
             vorangegangene Befehle gesetzt wurden
           
 - Wenn die  entsprechende Bedingung für den Sprung erfüllt ist, wird
             der Sprung ausgeführt, ansonsten der nächste Befehl
 
           - Die Bedingung ist in die Mnemonik kodiert. Es gibt mehrere Mnemoniks mit 
           der gleichen Funktion (nicht alle sind hier aufgeführt)
 
         
       
           - Befehle für bedingte Sprünge, die das Zero-Flag auswerten sind:
             
               | Befehl | Bedeutung |  Bedingung für Flags | 
               | JZ | Spring, wenn null ("jump if zero") | ZR=1 | 
               | JE | Spring, wenn gleich ("jump if equal") | ZR=1 | 
               | JNZ | Spring, wenn nicht null ("jump if not zero") | ZR=0 | 
 
               | JNE | Spring, wenn nicht gleich ("jump if not equal") | ZR=0 | 
   
             
            
         
         
        
      
   
    
     
       
         
Assemblerbefehle für bedingte Sprünge
       
       
         
           - Bedingte Sprünge nach dem Vergleich von vorzeichenlosen Zahlen:
             
               | Befehl | Bedeutung |  Bedingung | 
               | JB | Spring, wenn niedriger ("jump if below") | CY=1 | 
               | JNAE | Spring, wenn nicht höher oder gleich ("jump if not above or equal") | CY=1 | 
               | JAE | Spring, wenn höher oder gleich ("jump if above or equal") | CY=0 | 
               | JNB | Spring, wenn nicht niedriger ("jump if not below") | CY=0 | 
               | JBE | Spring, wenn niedr. gleich ("jump if below equal") | CY=1 oder ZR=1 | 
               | JNA | Spring, wenn nicht höher ("jump if not above") | CY=1 oder ZR=1 | 
               | JA | Spring, wenn höher ("jump if above") | CY=0 und ZR=0 | 
               | JNBE | Spring, wenn nicht niedriger oder gleich ("jump if not below or equal") | CY=0 und ZR=0 | 
             
            
         
         
        
      
    
    
     
       
         
Assemblerbefehle für bedingte Sprünge
       
       
         
           - Bedingte Sprünge nach dem Vergleich von vorzeichenbehafteten Zahlen:
             
               | Befehl | Bedeutung |  Bedingung für Flags | 
               | JG | Spring, wenn größer ("jump if greater") | ZR=0 und PL = OV | 
               | JNLE | Spring, wenn nicht kleiner oder gleich ("jump if not less or equal") | ZR=0 und PL = OV | 
                | JLE | Spring, wenn kleiner oder gleich ("jump if less or equal") | ZR=1 oder PL ≠ OV | 
               | JNG | Spring, wenn nicht größer ("jump if not greater") | ZR=1 oder PL ≠ OV | 
               | JL | Spring, wenn kleiner ("jump if less") | PL ≠ OV | 
               | JNGE | Spring, wenn nicht größer oder gleich ("jump if not greater or equal") | PL ≠ OV | 
               | JGE | Spring, wenn größer oder gleich ("jump if greater or equal") | PL = OV | 
               | JNL | Spring, wenn nicht kleiner ("jump if not less") | PL = OV | 
             
            
         
         
        
      
    
     
     
       
         
Assemblerbefehle für bedingte Sprünge
       
       
         
           - Bedingte Sprünge basierend auf bestimmten Flags:
             
               | Befehl | Bedeutung |  Bedingung | 
               | JC | Spring, wenn Carry gesetzt ("jump if carry") | CY=1 | 
               | JNC | Spring, wenn Carry nicht gesetzt ("jump if not carry") | CY=0 | 
               | JO | Spring, wenn Overflow gesetzt ("jump if overflow") | OV=1 | 
               | JNO | Spring, wenn Overflow nicht gesetzt ("jump if not overflow") | OV=0 | 
               | JS | Spring, wenn Sign gesetzt ("jump if sign") | PL=1 | 
               | JNS | Spring, wenn Sign nicht gesetzt ("jump if not sign") | PL=0 | 
             
            
         
         
        
      
    
     
     
       
         
Assemblerbefehle für bedingte Sprünge
       
       
         
           - Bedingte Sprünge basierend auf dem (E)CX-Register:
             
               | Befehl | Bedeutung |  Bedingung | 
               | JCXZ | Spring, wenn CX-Register null ist ("jump if register CX is zero") | CX=0 | 
               | JECXZ | Spring, wenn ECX-Register null ist ("jump if register ECX is zero") | ECX=0 | 
               | LOOP | Dekrementiere (E)CX; springe, wenn (E)CX nicht null ("decrement (E)CX; jump if (E)CX not zero") | (E)CX≠0 | 
             
            
         
         
        
      
     
    
     
       
         
Assemblerbefehle für bedingte Sprünge
       
       
         
           - 
           
 - Beispiel: JNA ("jump if not above")
int main() {
  __asm {
                    mov eax, 2
                    mov ecx, 3
                    cmp eax, ecx
                    jna ThisIsMyLabel
                    mov eax, 33
    ThisIsMyLabel:  mov ecx, 6
                    add eax, ecx; // eax = ?
  }
  return 0;
}   Quelldatei: main.cpp
            
          
        
            
     
     
     
       
         
Assemblerbefehle für bedingte Sprünge
       
       
         
           - Beispiel: Summe 1+2+3+ .... + n :
#include <stdio.h>
unsigned int smallGauss(unsigned int varA) { // version 1
  __asm
  {
                 mov eax, 0
                 mov ecx, varA
                 cmp ecx, 0
    ExecuteLoop: jz EndLoop
                 add eax, ecx 
                 sub ecx, 1
                 jmp ExecuteLoop;
    EndLoop: 
  }
}
int main() {
  unsigned int n = 5;
  unsigned int result = smallGauss(n); // call C-function "smallGauss"
  printf( "smallGauss(%d) = %d\n", n, result); // output result to console
  return 0;
} Quelldatei: main.cpp
            
         
        
      
     
     
       
         
Die Assemblerbefehle INC und DEC
       
       
         
         - Die arithmetischen Operationen 
inc und dec dienen zum Inkrementieren 
           bzw. Dekrementieren eines Speicher- oder Registerinhaltes um 1 
         - Aufgrund ihrer Geschwindigkeit und Lesbarkeit sind sie einer Addition von 1 mit
           dem Add-Befehl bzw. Subtraktion von 1 mit dem Sub-Befehl vorzuziehen
 
         
        - Wird der Code aus der vorherigen Implementierung der Funktion 
smallGauss angepasst, ergibt sich:
unsigned int smallGauss(unsigned int varA) { // version 2
  __asm
  {
                 mov eax, 0
                 mov ecx, varA
                 cmp ecx, 0
    ExecuteLoop: jz EndLoop
                 add eax, ecx 
                 dec ecx
                 jmp ExecuteLoop;
    EndLoop: 
  }
} Quelldatei: main.cpp
            
         
         
         
        
           
     
     
     
       
         
Die Assemblerbefehle INC und DEC
       
       
         
         - Die Befehle 
inc und dec beeinflussen die Flags: OV,PL,ZR,AC und 
            PE 
         - Interessant ist, dass das Carry-Flag nicht gesetzt wird
 
         - Dies ist nicht nötig, da das Zero-Flag und das Carry-Flag bei Addition von 1 immer den gleichen
           Wert haben. D.h. der Befehl
add ziel, 1
           setzt genau dann das Carry-Flag, wenn das Ergebnis null ist und 
           dementsprechend das Zero-Flag gesetzt wird (z.B. bei einem 8-Bit-Register für ziel=FFh) 
         - Bei Subtrahieren von 1 mit
sub ziel, 1
          wird genau dann das Carry-Flag gesetzt, wenn vorher ziel=0 war. Auch dies ist leicht zu prüfen, so
          dass auf das Carry-Flag verzichtet werden kann 
         
        
      
     
     
       
         
Der Assemblerbefehl LOOP
       
       
         
           - Der Befehl 
loop wurde bereits bei den bedingten Sprüngen erwähnt 
           
loop ziel
           Ziel kann eine Sprungmarke oder eine Adresse sein 
           Beim Aufruf des Befehls wird das (E)CX-Register dekrementiert und der Sprung ausgeführt, wenn (E)CX ungleich null ist
           Ist ziel eine 16-Bit Adresse, wird das CX- und bei 32-Bit das ECX-Register betrachtet
        Die Funktion smallGauss kann somit nochmal verbessert werden: (main.cpp)
unsigned int smallGauss(unsigned int varA) { // version 3
    __asm
    {
                   mov eax, 0
                   mov ecx, varA
                   jecxz EndLoop
     ExecuteLoop:  add eax, ecx
                   loop ExecuteLoop
     EndLoop:
    }
} 
           
                  
        
     
     
     
     
     
       
         
Der Assemblerbefehl MUL
       
       
         
           - Der Befehl 
mul führt eine Multiplikation für vorzeichenlose Zahlen aus 
           - Hierbei wird von dem üblichen Schema:
 
op ziel, quelle
             abgewichen, da das Ergebnis im Allgemeinen nicht in ein Register passt
            
           - Stattdessen wird das Ziel, welches mehrere Register umfassen kann, fest vorgegeben 
             und der Mul-Befehl benötigt nur die Quelle als Parameter:
             
mul quelle
        
            - Welche Register als Ziel verwendet werden hängt von der Bitbreite von 
quelle ab:
             
               | Bitbreite | Ziel | Operation | 
               | 8 | AX |  AX = AL * quelle | 
               | 16 | DX:AX |  DX:AX = AX * quelle | 
               | 32 | EDX:EAX |  EDX:EAX = EAX * quelle | 
             
            
           - Überschreitet das Ergebnis die Bitbreite von 
quelle werden Carry- und Overflow-Flag 
             gesetzt 
         
        
     
     
     
     
       
     
       
         
Der Assemblerbefehl MUL
       
       
         
           - Beispiel: 
             
int main() {
  __asm {
    mov cl, 16         ; // CL=10h (8-bit register)
    mov al, 8          ; // AL=08h  
    mul cl             ; // AX=0080h=128                Flags: OV=0, CY=0
    mov cx, 4000h      ; // CX=4000h (16-bit register)
    mov ax, 5000h      ; // AX=5000h
    mul cx             ; // DX:AX= 1400:0000            Flags: OV=1, CY=1   
    mov ecx, 7FFFFFFFh ; // ECX=7FFFFFFFh (32-bit register)
    mov eax, 4         ; // EAX=00000004h
    mul ecx            ; // EDX:EAX=00000001:FFFFFFFC   Flags: OV=1, CY=1   
  }
  return 0;
}Quelldatei: main.cpp
              
         
        
     
 
     
     
       
         
Der Assemblerbefehl DIV
       
       
         - Der Assemblerbefehl 
div führt eine Division aus
           div quelle
    
            
           - Das Ergebnis der Division ist ein ganzzahliger Anteil und ein Rest
 
           - Der Divisor  wird mit 
quelle angegeben 
           - Der Dividend muss in fest vorgegebenen Registern abgelegt werden.  Welche
             dies sind, ist abhängig von der Bitbreite von 
quelle:
              
               | Bitbreite | Dividend | Ganzzahliges Ergebnis | Rest | 
               | 8 | AX | AL = AX / quelle | AH = AX mod quelle | 
               | 16 | DX:AX |  AX = DX:AX / quelle |  DX = DX:AX mod quelle | 
               | 32 | EDX:EAX |  EAX = EDX:EAX / quelle |  EDX = EDX:EAX mod quelle | 
             
            
           - Die Division ist demnach so implementiert, dass sie die exakte Umkehrfunktion einer Multiplikation realisiert
 
           - Flags werden nicht verändert
 
         
        
     
       
       
     
       
         
Der Assemblerbefehl DIV
       
       
         
           - Beispiel: 
             
int main() {
  __asm {
    mov cx, 4000h      ; // CX=4000h (16-bit register)
    mov ax, 5000h      ; // AX=5000h
    mul cx             ; // DX:AX=1400:0000            Flags: OV=1, CY=1   
    div cx             ; // AX=5000h DX=0000h
    mov dx, 0000h      ;
    mov ax, 0002h      ; // DX:AX=0000:0002h
    mov cx, 10h        ; // CX=10h
    div cx             ; // AX=0000h DX=0002h
    mov dx, 7FFFh      ;
    mov ax, 0000h      ; // DX:AX=7FFF:0000h
    mov cx, 2h         ; // CX=2h
    div cx             ; // result does not fit in AX, throws divide error exception
  }
  return 0;
}Quelldatei: main.cpp
              
         
        
     
       
       
       
         
           
Der Assemblerbefehl DIV
         
         
           
             - Beispiel: Größter gemeinsamer Teiler (ggT) mit dem Euklidischen Algorithmus:
               unsigned int ggT_C(unsigned int a, unsigned int b) {
  unsigned int res = 0;
  while(b != 0) { // while b not zero
    res = (a % b); // res = (a mod b)
    a = b; 
    b = res;
  }
  return a;
}
              
             - Z.B. Größter gemeinsamer Teiler von $525$ und $399$:
               
               $\begin{align}
               525 &= 1 \cdot 399 + 126\\
               399 &= 3 \cdot 126 + 21\\
               126 &= 6 \cdot 21  + 0 \\
               \end{align}$
               
               
                  Ergebnis: $21$
               
                
           
          
       
       
       
     
       
         
Der Assemblerbefehl DIV
       
       
         
           - Größter gemeinsamer Teiler (ggT) mit dem Euklidischen Algorithmus in Assembler
             
unsigned int ggT_Asm(unsigned int a, unsigned int b) {
  __asm {     
               mov edx, 0    ;
               mov eax, a    ; // EDX:EAX = a
               mov ecx, b    ; // ECX = b
    WhileLoop: cmp ecx, 0    ;
               jz EndLoop    ; // jump to "done" if ECX == 0
               div ecx       ; 
               mov eax, ecx  ; // EAX = ECX
               mov ecx, edx  ; // ECX = (a mod b)
               mov edx, 0    ; // EDX = 0 for next "div" command
               jmp WhileLoop ; 
    EndLoop:
  }
}Quelldatei: main.cpp
            
         
        
     
       
       
       
         
           
Der Assemblerbefehl IMUL
         
         
           
             - Der Befehl 
IMUL führt eine Multiplikation von vorzeichenbehafteten Zahlen aus 
             - Es gibt drei verschiedene Varianten:
               
             
 
             - Bei der 2. und 3. Variante werden die niederwertigen Bits des Ergebnisses abgeschnitten falls es nicht in 
ziel
               passen sollten. Carry- und Overflow-Flag werden in diesem Fall gesetzt 
              - Quelldatei für das Beispiel auf der nächsten Folie: 
             main.cpp
 
           
          
       
       
       
       
         
           
Der Assemblerbefehl IMUL
         
         
#include <stdio.h>
int main() {
  int result = 0;
  __asm {
    mov eax, -2     
    mov ecx, 800
    imul ecx
    mov result, eax   // EDX is ignored here 
  }
  printf("result=%d \n", result);
  __asm {
    mov eax, -2     
    mov ecx, 800
    imul eax, ecx
    mov result, eax
  }
  printf("result=%d \n", result);
  __asm {   
    mov ecx, 800
    imul eax, ecx, -2
    mov result, eax
  }
  printf("result=%d \n", result);
  return 0;
}
          
       
       
       
       
       
         
           
Der Assemblerbefehl IDIV
         
         
           
             - Der Befehl 
IDIV führt eine Division von vorzeichenbehafteten Zahlen aus 
             - Der Befehl wird äquivalent zum Befehl 
DIV verwendet
               #include <stdio.h>
int main() {
  int result = 0;
  int remainder = 0;
  __asm {
    mov edx, 0
    mov eax, 800     
    mov ecx, -3
    idiv ecx
    mov result, eax
    mov remainder, edx
  }
  printf("result=%d \n", result);
  printf("remainder=%d \n", remainder);
  return 0;
}Quelldatei: main.cpp
              
           
          
       
   
     
     
       
         
Der Assemblerbefehl NEG
       
       
     
       
     
     
       
         
Die Assemblerbefehle AND, OR, NOT
       
       
         
           - Die Assemblerbefehle 
and, or, und not führen
             elementare logische Grundoperationen aus, wie diese aus der booleschen Algebra bekannt sind 
           - Konjunktion (AND): $y = a \land b$
             
               
                   | $a$ | $b$ | $y = a \land b$ | 
                 
                 
                   | 0 | 0 | 0 | 
                 
                 
                   | 0 | 1 | 0 | 
                 
                 
                   | 1 | 0 | 0 | 
                 
                 
                   | 1 | 1 | 1 | 
                 
               
            
           - Disjunktion (OR): $y = a \lor b$
             
               
                   | $a$ | $b$ | $y = a \lor b$ | 
                 
                 
                   | 0 | 0 | 0 | 
                 
                 
                   | 0 | 1 | 1 | 
                 
                 
                   | 1 | 0 | 1 | 
                 
                 
                   | 1 | 1 | 1 | 
                 
               
            
           - Negation (NOT): $y = \lnot a$
             
           
 
         
        
     
     
     
     
       
         
Die Assemblerbefehle AND, OR, NOT
       
       
         
          - Dabei wird jedes Bit einzeln betrachtet
 
          - Das Carry-Flag 
CY und Overflow-Flag OF werden auf null gesetzt. Die Flags PL, ZR und PE  werden gemäß
            des Ergebnisses gesetzt. Das Aux. Carry AC wird nicht beeinflusst. 
          - Beispiel:
int main() {
  unsigned int a = 0x000078F1;
  unsigned int b = 0x00007F8F;
  __asm {
    mov eax, a    ; // EAX=000078F1h
    not eax       ; // EAX=FFFF870Eh
    and eax, b    ; // EAX=0000070Eh     
    mov edx, b    ; // EDX=0000070Eh     
    not edx       ; // EDX=FFFF8070h   
    and edx, a    ; // EDX=00000070h   
    or eax, edx   ; // EAX=0000077Eh   
  }
  return 0;
}
           
           - Frage: Was wurde hier gerade umständlich implementiert?
             
         
        
          
  
     
     
       
         
Der Assemblerbefehl XOR
       
     
         
                - Antwort: XOR
int main() {
  unsigned int a = 0x000078F1;
  unsigned int b = 0x00007F8F;
  __asm {
    mov eax, a    ; // EAX=000078F1h
    not eax       ; // EAX=FFFF870Eh
    and eax, b    ; // EAX=0000070Eh     
    mov edx, b    ; // EDX=0000070Eh     
    not edx       ; // EDX=FFFF8070h   
    and edx, a    ; // EDX=00000070h   
    or eax, edx   ; // EAX=0000077Eh   
  }
  // simple alternative
  __asm {
    mov eax, a     ; // EAX=000078F1h
    xor eax, b     ; // EAX=0000077Eh
  }
  return 0;
} Quelldatei: main.cpp 
                 
              
        
         
     
     
     
       
         
Die Assemblerbefehle SHL und SHR
       
       
     
     
     
     
       
         
Die Assemblerbefehle SAL und SAR
       
       
         
           - 
             Der Befehle 
sal ("Shift arithmetic left") und sar ("Shift arithmetic right") entsprechen
             den Befehlen shl und shr. Der einzige Unterschied ist, dass
             bei sar statt einer 0 eine 1 hineingeschoben wird 
                  
           
         
        
     
     
    
     
       
         
Der Assemblerbefehl XCHG
       
       
         
           - Der Befehl 
xchg ("exchange") tauscht den Inhalt der beiden Operanden aus
            
           
           - Beispiel:
int main() {
  __asm {
    mov eax, 1    ; // EAX=1
    mov edx, 2    ; // EDX=2
    xchg eax, edx ; // EDX=1, EAX=2 
  }
  return 0;
} Quelldatei: main.cpp            
            
           
           
         
        
     
  
     
     
       
         
Die Assemblerbefehle BT, BTS, BTR, BTC
       
       
         
           Die "Bit Test"-Befehle, bt, bts, btr und btc, kopieren das
           durch bitIndex indizierte Bit ins Carry-Flag und modifizieren dieses Bit in ziel entsprechend der Semantik der Mnemonik
           
bt ziel, bitIndex
           
             | Befehl | Wirkung auf das Carry cy | Wirkung auf das indizierte Bit | 
             bt ("bit test") | cy=ziel[bitIndex] | ziel[bitIndex] unbeeinflusst | 
             bts ("bit test and set") |  cy=ziel[bitIndex] | ziel[bitIndex]=1 | 
             btr ("bit test and reset") |  cy=ziel[bitIndex] | ziel[bitIndex]=0 | 
             btc ("bit test and complement)") |  cy=ziel[bitIndex] | ziel[bitIndex] = not(ziel[bitIndex]) | 
           
           
         
        
     
    
     
     
       
         
Die Assemblerbefehle CLC und STC
       
       
         
           - Die Assemblerbefehle 
clc ("clear carry") und stc ("set carry") löschen bzw. setzen das Carry-Flag 
           - Beispiel:
int main() {
  __asm {
    stc             ; // CY=1
    clc             ; // CY=0
    mov ax, 00FFh   ; // AX=00FFh
    bt  ax, 0       ; // CY=1
    bt  ax, 7       ; // CY=1
    bt  ax, 8       ; // CY=0
    bts ax, 8       ; // CY=0, AX=01FFh
  }
  return 0;
} Quelldatei: main.cpp            
                       
         
        
     
    
    
     
       
         
Der Assemblerbefehl NOP
       
       
         
           - Der Assemblerbefehl 
nop ("no operation") führt keine Operation aus 
           - Er dient z.B. dazu, die Ablaufgeschwindigkeit von Maschinespracheprogrammen zu beeinflussen, da ein NOP-Befehl jeweils
             eine definierte Verzögerung erzeugt
 
         
        
     
    
    
     
       
         
Weitere Assemblerbefehle der x86 Prozessorfamilie
       
       
         
           - Die hier beispielhaft gezeigten Befehle sind natürlich nur ein Auszug aus
           den insgesamt ca. 200 Mehrzweckbefehlen und weiteren 300 Spezialbefehlen
 
           - Für viele einfache Assemblerprogramme sind die hier gezeigten Befehle jedoch bereits ausreichend
 
           - Einige weitere Befehle werden in den folgenden Kapiteln vorgestellt
 
           - Die komplette Dokumentation der x86 Architektur steht auf den Webseiten von Intel zur Verfügung:
             
             
           
           
 
           
         
        
     
  
    
    
      
        
Die x86-Befehlsreferenz
      
      
        
          - Die Dokumentation eines Assembler-Befehls besteht jeweils aus eine großen Tabelle, die
            zeigt für welche Operanden der Befehl vorhanden ist
 
          - Hier z.B. die Tabelle für den Befehl 
add (Seite 3-31 der  
            Befehlsreferenz):
         
        
       
    
    
    
    
      
        
Die x86-Befehlsreferenz
      
      
        
          - Es fällt auf, dass je nach Art der angegebenen Operanden ein anderer Opcode verwendet wird
 
          - D.h. der verwendeten Maschinenbefehl (Opcode) bestimmt sich zum Einen aus der Mnemonik,
            ist aber auch abhängig von den Operanden
 
          - Abkürzungen:
            
              r8, r16, r32 Operand ist eine 8-, 16-, bzw. 32-Bit-Mehrzweckregister  
              m8, m16, m32 Operand ist eine Speicheradresse (engl. "memory")  
              m/r8, m/r16, m/r32 Operand ist ein Register oder eine Speicheradresse  
              imm8, imm16, imm32 Operand ist eine konstante vorzeichenbehaftete Zahl (engl. "immediate value")  
            
           
          - Eine komplette Liste der Abkürzungen ist in Kapitel 3.1.1.3 (Seite 3-5) der Befehlsreferenz angegeben
 
          - Neben der Tabelle und der Beschreibung eines Befehls ist für einen Programmierer besonders der Abschnitt über die 
            veränderten Flags ("Flags Affected") des jeweiligen Befehls wichtig
 
        
       
    
        
    
    
      
        
Verkürzte Befehlsreferenz für die Klausur (1 von 2)
      
          
    
    | Assemblerbefehl  |  Beschreibung  | 
    ADC ziel, quelle |  Addiere mit Übertrag aus vorangegangener Addition  | 
    ADD ziel, quelle |  ziel = ziel + quelle | 
		AND ziel, quelle |  Logische Und-Verknüpfung von ziel und quelle  | 
		CMP x, y |  Vergleiche x und y  | 
		DEC x  |  Dekrementiere x  | 
		DIV op  |  Teile durch op  | 
		IDIV op  |  Vorzeichenbehaftete, ganzzahlige Division  | 
		IMUL op  |  Vorzeichenbehaftete, ganzzahlige Multiplikation  | 
		INC x  |  Inkrementiere x | 
		JA sprungziel  |  Springe, wenn größer  | 
		JAE sprungziel  |  Springe, wenn größer oder gleich  | 
		JB sprungziel  |  Springe, wenn kleiner  | 
		JBE sprungziel  |  Springe, wenn kleiner oder gleich  | 
		JECXZ sprungziel  |  Springe, wenn ECX gleich 0  | 
		JE sprungziel  |  Springe, wenn gleich  | 
		JMP sprungziel  |  Unbedingter Sprung  | 
           
    
    
    
    
      
        
Verkürzte Befehlsreferenz für die Klausur (2 von 2)
      
          
    
    | Assemblerbefehl  |  Beschreibung  | 
		JO sprungziel  |  Springe, wenn Überlauf  | 
		JZ sprungziel  |  Springe, wenn gleich 0  | 
		LOOP sprungziel  |  Dekrementiere ECX, springe falls ECX ungleich 0  | 
		MOV ziel, quelle  |  Kopiere quelle nach ziel  | 
		MUL op  |  Multipliziere mit op | 
		NEG ziel  |  Negiere Wert in ziel mit Zweierkomplement  | 
		NOT ziel  |  Logische Negation von ziel  | 
		OR ziel, quelle  |  Logische Oder-Verknüpfung von ziel und quelle  | 
		POP ziel  |  Hole obersten Wert vom Stack  | 
		PUSH quelle  |  Lege Wert aus quelle auf Stack  | 
		SAL ziel, schrittzahl  |  Arithmetische, bitweise Verschiebung nach links  | 
		SAR ziel, schrittzahl  |  Arithmetische, bitweise Verschiebung nach rechts  | 
		SHL ziel, schrittzahl  |  Logische, bitweise Verschiebung nach links  | 
		SHR ziel, schrittzahl  |  Logische, bitweise Verschiebung nach rechts  | 
    SUB ziel, quelle  |  ziel = ziel - quelle  | 
		XOR ziel, quelle  |  Logische XOR-Verknüpfung von ziel und quelle | 
 
           
    
      
      
        
          
Gibt es Fragen?
        
        
          
          
            Anregungen oder Verbesserungsvorschläge können auch gerne per E-mail
            an mich gesendet werden: Kontakt
          
          
          
            Weitere Vorlesungsfolien