892 lines
32 KiB
TeX
892 lines
32 KiB
TeX
\section{Motivation}
|
|
|
|
\begin{definition}[Übersetzer]
|
|
Ein \vocab{Übersetzer} erzeugt zu einem Programm in einer Quellsprache ein äquivalentes Programm in einer Zielsprache.
|
|
Ist die Quellsprache mächtiger als die Zielsprache, so wird der Übersetzer auch als \vocab{Compiler} bezeichnet.
|
|
|
|
\begin{figure}[H]
|
|
\centering
|
|
\begin{tikzcd}
|
|
\text{Konkretes Programm} & \text{Wirkung der Programmausführung}\\
|
|
\text{Quellsprache} \arrow{r}{\text{Sprachdefinition}} \arrow{d}{\text{Übersetzung}}& \text{Abstrakte Maschine} \arrow{d}{\text{gleiche Ergebnisse}} \\
|
|
\text{Zielsprache} \arrow{r}{\text{Maschinenbeschreibung}} & \text{Reale Maschine}
|
|
\end{tikzcd}
|
|
\end{figure}
|
|
\end{definition}
|
|
|
|
|
|
Selbst in Assembler zu programmieren, wird eher selten benötigt, ggf. für Embedded Devices und hoch optimierten Code. Allerdings hilft ein Verständnis von Assembler zum besseren Verständnis von Hochsprachen. Ferner ermöglicht es, Malware zu analysieren, Reverse Engineering, Patchen und Cracken.
|
|
|
|
\subsection{Programmierung in Maschinensprache}
|
|
|
|
Rechner verwenden intern sehr elementare Befehle:
|
|
\begin{itemize}
|
|
\item Zugriff auf Speicherzellen
|
|
\item Zugriff auf (wenige) Register
|
|
\item Arithmetische und logische Operationen
|
|
\item Sprünge zu anderen Befehlen
|
|
\end{itemize}
|
|
|
|
Jede CPU besitzt einen spezifische Menge an Befehlen. Intern werden diese binär codiert. Da dies, wie auch hexadezimale Codierung, für Menschen nicht zumutbar ist, existieren Assemblersprachen, die die Darstellung durch \vocab{mnemonische Symbole} erleichtern.
|
|
Mit Assemblersprache kann angenehmer als mit Maschinencode gearbeitet werden.
|
|
Allerdings sind auch Assemblersprachen stark von einer bestimmten Maschine abhängig.
|
|
Wird ein Assemblerprogramm für eine andere Maschine benötigt, so muss es vollständig neugeschrieben werden.
|
|
|
|
|
|
\subsection{Maschinenunabhängige Programmierung}
|
|
Um Unabhängigkeit von der konkreten Maschine zu erlangen, werden in höheren Programmiersprachen mächtigere Befehle definiert, die automatisch in Assembler-Sprachen übersetzt werden können und nicht vom spezifischen Befehlssatz einer CPU abhängen.
|
|
Damit sind diese Programme (theoretisch) auf allen Rechnern einsetzbar.
|
|
Praktisch existieren häufig Spracherweiterungen, die von maschinenspezifischen Charakteristika Gebrauch machen.
|
|
|
|
\section{Bezug zur Systemnahen Informatik}
|
|
|
|
\subsection{Der Von-Neumann-Rechner}
|
|
|
|
\begin{definition}[Von-Neumann-Rechner]
|
|
Der \vocab{Von-Neumann-Rechner} besteht aus fünf Funktionseinheiten:
|
|
\begin{itemize}
|
|
\item \vocab{Steuerwerk} (control unit): Lädt und decodiert Programmbefehle, Koordiniert die Befehlsausführung
|
|
\item \vocab{Arithmetisch-logische Einheit} (ALU): Führt arithmetische und logische Operationen unter Kontrolle des Steuerwerks aus. Das Steuerwerk stellt hierfür die Operanden bereit.
|
|
\item \vocab{Speicher}: Der Speicher ist in nummerierte, gleich große Zellen geteilt. Über die jeweilige Adresse kann auf Zellinhalte lesend oder schreibend zugegriffen werden.
|
|
\item \vocab{Eingabewerk}
|
|
\item \vocab{Ausgabewerk}
|
|
\end{itemize}
|
|
|
|
\todoimg{1 S.12}
|
|
|
|
\end{definition}
|
|
|
|
\subsection{BORIS}
|
|
|
|
\todoimg{1 S.13}
|
|
|
|
Ein einfaches Programm für BORIS wäre
|
|
\begin{lstlisting}
|
|
LOAD A,[10] // Lade den Inhalt von Speicherzelle 10 in Akkumulator
|
|
ADD A,[11] // Addiere den Inhalt von Speicherzelle 11 zum Inhalt des Akkumulator
|
|
STORE A,[12] // Lege Inhalt des Akkumulators in Speicherzelle 12 ab
|
|
\end{lstlisting}
|
|
|
|
\subsection{Gestalt von Maschinenbefehlen}
|
|
|
|
Maschinenbefehle werden in Worten oder Halbworten gespeichert. Ein Befehl hat prinzipiell die folgende Gestalt:
|
|
|
|
\begin{table}[htpb]
|
|
\centering
|
|
\label{tab:maschinenbefehl}
|
|
\begin{tabular}{|c|c|c|c|c|c|c|}
|
|
\hline
|
|
Format & Op-Code & Daten $1$ & \dots & Daten $k$ & Ziel & Folge \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
\begin{itemize}
|
|
\item Format: Angabe der Länge und Position der einzelnen Felder. Kann entfallen, falls eindeutig durch den Op-Code bestimmt.
|
|
\item Op-Code: Angabe der Operation
|
|
\item Daten: Wo sind die Operanden?
|
|
\begin{itemize}
|
|
\item unmittelbar (immediate): Daten als Konstanten im Maschinencode
|
|
\item implizit: Akkumulator oder Stack
|
|
\item direkt: Angabe der Adressdarstellung im Befehl
|
|
\item indirekt: Angabe der Adresse der Speicherzelle, welche die Adresse des Operanden enthält
|
|
\end{itemize}
|
|
\item Ziel: Wo soll das Ergebnis gespeichert werden?
|
|
\begin{itemize}
|
|
\item ggf. Überdeckung (Quelle ist auch Ziel)
|
|
\item ggf. Implizierung (Akkumulator oder Stack als Ziel)
|
|
\end{itemize}
|
|
\item Folge: Adresse des nächsten Befehls. Entfällt bei sequentieller Abarbeitung.
|
|
\end{itemize}
|
|
|
|
Da lange Befehle nicht effizient bearbeitet werden können, werden in der Regel nur wenige Adressen verwendet.
|
|
Die häufigsten Formen sind
|
|
\begin{itemize}
|
|
\item 0-Adress-Format: Op-Code
|
|
\item 1-Adress-Format: Op-Code, Quelle = Ziel
|
|
\item 2-Adress-Format: Op-Code, Quelle1, Quelle2 = Ziel
|
|
\item 3-Adress-Format: Op-Code, Quelle1, Quelle2, Ziel
|
|
(selten, eine Variante von \code{imul})
|
|
\end{itemize}
|
|
\subsection{Assembler-Sprachen}
|
|
|
|
Eine Assembler-Sprache ist eine maschinenorientierte Programmiersprache. Statt der binären Darstellung von Maschinensprachen werden mnemonische Symbole eingesetzt. Ein Befehl einer Assembler-Sprache umfasst:
|
|
\begin{itemize}
|
|
\item Bezeichnung der durchzuführenden Operation
|
|
\item ggf. Angaben zu den Operanden
|
|
\item ggf. Marken (d.h. symbolische Adressen) zur Kennzeichnung von der Programmzeile
|
|
\end{itemize}
|
|
|
|
Darüber hinaus stellen Assembler-Sprachen oft auch \vocab{Makros} zur Verfügung. Ein Makro fasst mehrere Befehle oder Deklarationen zu einer Einheit zusammen. Dem Makro wird ein eindeutiger Bezeichner zugeordnet. Wenn der Bezeichner im Programmtext auftaucht, wird er vom Assembler durch den zugehörigen Text ersetzt, bevor die Umwandlung in Maschinencode vorgenommen wird. Ferner sind parametrisierte Makrobefehle möglich.
|
|
|
|
Ein Assembler-Programm muss vor der Ausführung in ein entsprechendes Maschinenprogramm übersetzt werden. Der hierzu erforderliche \vocab{Assembler} muss:
|
|
\begin{itemize}
|
|
\item Befehle und Operanden in Binärcode umsetzen
|
|
\item Marken in Adressen umrechnen
|
|
\item ``Pseudobefehle'' bearbeiten (z.B. Reservierung von Speicherplatz)
|
|
\end{itemize}
|
|
|
|
\subsection{Pseudoassembler ($\alpha$-Notation)}
|
|
|
|
Da Assembler stark maschinenabhängig ist, wurde im Rahmen der Systemnahen Informatik die sogenannte $\alpha$-Notation verwendet.
|
|
|
|
\begin{table}[htpb]
|
|
\centering
|
|
\label{tab:alphanotation}
|
|
\begin{tabular}{ll}
|
|
$\alpha $ & Akkumulator\\
|
|
$\rho(i)$ & Inhalt der Speicherzelle mit Adresse $i$\\
|
|
$+,-,\cdot, /$ & Operationen
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
In der $\alpha$-Notation können Marken zum Kennzeichnen von Zellen des Datenspeichers sowie des Programmspeichers verwendet werden.
|
|
|
|
|
|
\subsection{Vom Assemblerprogramm zum Maschinencode}
|
|
|
|
Zum Übersetzen von Assembler-Code in ein Objektprogramm wird ein sogenannter \vocab{Assembler} benötigt. Dieser setzt die mnemonischen Symbole in Binärcode um, löst symbolische Adressen auf und wandelt Literale in Binärdarstellung um.
|
|
Ein Assembler benötigt i.d.R.~mindestens zwei Durchgänge:
|
|
\begin{enumerate}[1.]
|
|
\item Durchgang:
|
|
\begin{itemize}
|
|
\item Bestimmung der Länge von Maschineninstruktionen
|
|
\item Verwaltung eines Adresszählers
|
|
\item Zuordnung symbolischer Adressen
|
|
\item Zuordnung von Literalen
|
|
\item Verarbeitung einiger Assembler-Instruktionen
|
|
\end{itemize}
|
|
\item Durchgang:
|
|
\begin{itemize}
|
|
\item Heranziehung der Symbolwerte
|
|
\item Erzeugung von Maschineninstruktionen
|
|
\item Erzeugung von Daten (Konstanten)
|
|
\item Verarbeitung der restlichen Assembler-Instruktionen
|
|
\end{itemize}
|
|
\end{enumerate}
|
|
|
|
Hierfür werden folgende Tabellen benötigt:
|
|
\begin{itemize}
|
|
\item Maschineninstruktionen (statisch)
|
|
\item Assemblerinstruktionen (statisch)
|
|
\item Symboltabelle
|
|
\item ggf. weitere
|
|
\end{itemize}
|
|
|
|
|
|
\section{Allgemeines zur 80x86 Assembler-Programmierung}
|
|
Online-Literaturhinweise:
|
|
\begin{itemize}
|
|
\item Assembler unter Linux: \url{https://asm.sourceforge.net/}
|
|
\item PC-Assembler Tutorial: \url{https://pacman128.github.io/pcasm/}
|
|
\item Linux Assembly HOWTO \url{https://www.tldp.org/HOWTO/Assembly-HOWTO/}
|
|
\item NASM Documentation: \url{https://nasm.us/doc/nasmdoc0.html}
|
|
\item Intel CodeTable: \url{https://www.jegerlehner.ch/intel/index.html}
|
|
\item System Call Table: \url{https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/}
|
|
\end{itemize}
|
|
|
|
\subsection{x86-64 Architektur}
|
|
|
|
Im Folgenden wird mit dem 64-Bit x64-Befehlssatz im Protected Mode gearbeitet. x86-Prozessoren adressieren den Speicher byteweise. Instruktionen können auf einem oder mehreren Bytes im Arbeitsspeicher arbeiten. Je nach Anzahl der Bytes werden die folgenden Begriffe verwendet:
|
|
|
|
\begin{table}[htpb]
|
|
\centering
|
|
\begin{tabular}{ccc}
|
|
\textbf{Name} & \textbf{Wert} & \textbf{Intel Syntax}\\
|
|
byte & 1 Byte & \code{db}, \code{.byte}\\
|
|
\vocab{word} & 2 Byte & \code{dw}, \code{.word}\\
|
|
\vocab{double word} (\vocab{dword}) & 4 Byte & \code{dd}, \code{.double}\\
|
|
\vocab{quad word} (\vocab{qword}) & 8 Byte & \code{dq}, \code{.quad} \\
|
|
\vocab{paragraph} & 16 Byte &
|
|
\end{tabular}
|
|
\end{table}
|
|
\begin{itemize}
|
|
\item word $\coloneqq 2 $ Byte
|
|
\item double word (dword) $\coloneqq 4$ Byte
|
|
\item quad word $\coloneqq 8$ Byte
|
|
\item paragraph $\coloneqq 16$ Byte
|
|
\end{itemize}
|
|
|
|
Die \vocab{Bytereihenfolge} ist \vocab{Little Endian}, d.h. das niedrigstwertige Byte wird an erster Stelle gespeichert.
|
|
|
|
\begin{warning}
|
|
Das ist nicht die Network Byte Order!
|
|
\end{warning}
|
|
|
|
|
|
\subsubsection{Register}
|
|
|
|
Neben einem Register sind in x86-64 i.d.R. auch die untere Hälfte, das untere Viertel und die letzten beiden Byte einzeln adressierbar:
|
|
\begin{table}[htpb]
|
|
\centering
|
|
\caption{Register}
|
|
\label{tab:register}
|
|
\begin{tabular}{cccccc}
|
|
\textbf{Verwendung} & 64bit & 32bit & 16bit& oberes Byte & unteres Byte\\
|
|
\hline
|
|
Accumulator & rax & eax & ax & ah & al\\
|
|
Allgemein & rbx & ebx & bx &bh & bl\\
|
|
Counter für Schleifen & rcx & ecx & cx & ch & cl\\
|
|
Datenregister (mul, div, IO) & rdx & edx & dx & dh & dl\\
|
|
String-Op Source & rsi & esi & si & \\
|
|
String-Op Dest & rdi & edi & di &\\
|
|
Basepointer & rbp & ebp & bp \\
|
|
Stackpointer & rsp & esp & sp \\
|
|
Allg. (X $\in \{8,\ldots,15\}$ ) & rX & rXd & rXw & & rXb\\
|
|
Flags & rflags & efalgs & flags \\
|
|
Segmentregister & \multicolumn{5}{c}{im Protected Mode nicht verwendbar} \\
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Im \vocab{Statusregister} werden diverse Flags gespeichert. In der CodeTable lässt sich nachlesen, welcher Befehl welche Flags liest oder beeinflusst.
|
|
|
|
\begin{table}[htpb]
|
|
\centering
|
|
\caption{Control Flags (Einfluss auf Ausführung von Instruktionen)}
|
|
\label{tab:controlflags}
|
|
\begin{tabular}{cll}
|
|
& \textbf{Name} & \textbf{Bedeutung des Wert 1} \\
|
|
D & Direction (String-Op) & von hohen nach niedrigen Adressen\\
|
|
I & Interrupt & Interrupt enabled\\
|
|
T & Trap/ Trace & Debugging Single Step/Trace Modus\\
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
\begin{table}[htpb]
|
|
\centering
|
|
\caption{Status Flags}
|
|
\label{tab:statusflags}
|
|
\begin{tabular}{cll}
|
|
& \textbf{Name} & \textbf{Bedeutung des Wert 1} \\
|
|
C & Carry & Unsigned Op. zu groß oder $<0$\\
|
|
O & Overflow & Signed Op. zu groß oder zu klein\\
|
|
S & Sign & negativ\\
|
|
Z & Zero & Ergebnis ist $0$\\
|
|
A & Aux. Carry für BCD-Operationen \\
|
|
P & Parity & Gerade Anzahl an Einsen\\
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
\subsubsection{Befehle}
|
|
Operationen lassen sich in verschiedene Mengen einteilen:
|
|
\begin{itemize}
|
|
\item Data movement instructions -Verschieben von Daten in und von Registern:
|
|
|
|
\code{mov, lea, les, push, pop, pushf, popf}
|
|
\item Conversions - Konvertierungen Byte, Word, Double, Extended Double:
|
|
|
|
\code{cbw, cwd, cwde}
|
|
\item Arithmetik
|
|
|
|
\code{add, inc, sub, dec, cmp, neg, mul, imul, div, idiv}, \sout{\code{lea}}
|
|
\item Logical, shift, rotate and bit instructions
|
|
|
|
\code{and, or, xor, not, shl, shr, rcl, rcr}
|
|
\item I/O instructions
|
|
|
|
\code{in, out}
|
|
\item String instructions
|
|
|
|
\code{movs, stos, lods}
|
|
\item Program flow control instructions
|
|
|
|
\code{jmp, call, ret, (conditional jumps)}
|
|
\item Miscellaneous instructions
|
|
|
|
\code{clc, stc, cmc}
|
|
|
|
\end{itemize}
|
|
|
|
\todo{move to appendix?}
|
|
\begin{longtable}{ccc}
|
|
\caption{Wichtige x86 Befehle}\\
|
|
\textbf{Name} & \textbf{Kommentar} & \textbf{Syntax}\\
|
|
\hline
|
|
\endhead
|
|
\code{MOV} & move (copy) & \code{MOV Dest, Source}\\
|
|
\code{XCHG} & exchange & \code{XCHG Op1, Op2}\\
|
|
\hline
|
|
\code{STC} & set carry & \code{STC}\\
|
|
\code{CLC} & clear carry & \code{CLC} \\
|
|
\code{CMC} & complement carry & \code{CMC} \\
|
|
\code{STD} & set direction & \code{STD} \\
|
|
\code{CLD} & clear direction & \code{CLD} \\
|
|
\code{STI} & set interrupt & \code{STI} \\
|
|
\code{CLI} & clear interrupt & \code{CLI} \\
|
|
\code{PUSH} & push onto stack & \code{PUSH Source} \\
|
|
\code{PUSHF} & push flags & \code{PUSHF} \\
|
|
\code{PUSHA} & push all gerneral registers & \code{PUSHA} \\
|
|
\code{POP} & pop from stack & \code{POP Dest} \\
|
|
\code{POPF} & pop flags & \code{POPF} \\
|
|
\code{POPA} & pop all general registers & \code{POPA} \\
|
|
\hline
|
|
\code{CBW} & convert byte to word & \code{CBW} \\
|
|
\code{CWD} & convert word to double & \code{CWD} \\
|
|
\code{CWDE} & convert word to extended double & \code{CWDE} \\
|
|
\hline
|
|
\code{IN} & input & \code{IN Dest, Port} \\
|
|
\code{OUT} & output & \code{OUT Port, Source}\\
|
|
\hline
|
|
\code{ADD} & add & \code{ADD Dest, Source} \\
|
|
\code{ADC} & add with carry & \code{ADC Dest, Source} \\
|
|
\code{SUB} & subtract & \code{SUB Dest, Source} \\
|
|
\code{SBB} & subtract with borrow & \code{SBB Dest, Source} \\
|
|
\code{DIV} & divide (unsigned) & \code{DIV Op}\\
|
|
\code{IDIV} & signed integer divide & \code{IDIV Op} \\
|
|
\code{MUL} & multiply (unsigned) & \code{MUL Op} \\
|
|
\code{IMUL} & signed integer multiply & \code{IMUL Op} \\
|
|
\code{INC} & increment & \code{INC Op} \\
|
|
\code{DEC} & decrement & \code{DEC Op} \\
|
|
\code{CMP} & compare & \code{CMP Op1, Op2} \\
|
|
\hline
|
|
\code{SAL} & shift arithmetic left & \code{SAL Op, Quantity} \\
|
|
\code{SAR} & shift arithmetic right & \code{SAR Op, Quantity} \\
|
|
\code{RCL} & rotate left through carry & \code{RCL Op, Quantity} \\
|
|
\code{RCR} & rotate right through carry & \code{RCR Op, Quantity} \\
|
|
\code{ROL} & rotate left & \code{ROL Op, Quantity} \\
|
|
\code{ROR} & rotate right & \code{ROR Op, Quantity}\\
|
|
\hline
|
|
\code{NEG} & negate (two-complement) & \code{NEG Op} \\
|
|
\code{NOT} & invert each bit & \code{NOT Op} \\
|
|
\code{AND} & logical and & \code{AND Dest, Source} \\
|
|
\code{OR} & logical or & \code{OR Dest, Source} \\
|
|
\code{XOR} & logical xor & \code{XOR Dest, Source} \\
|
|
\code{SHL} & shift logical left & \code{SHL Op, Quantity} \\
|
|
\code{SHR} & shift logical right & \code{SHR Op, Quantity} \\
|
|
\hline
|
|
\code{NOP} & no operation & \code{NOP} \\
|
|
\code{LEA} & load effective address & \code{LEA Dest, Source} \\
|
|
\code{INT} & interrupt & \code{INT Nr}\\
|
|
\hline
|
|
\code{CALL} & call subroutine & \code{CALL Proc}\\
|
|
\code{RET} & return from subroutine & \code{RET} \\
|
|
\code{JMP} & jump & \code{JMP Dest}\\
|
|
\code{JE} & jump if equal & \code{JE Dest}\\
|
|
\code{JZ} & jump if zero & \code{JZ Dest}\\
|
|
\code{JCXZ} & jump if CX zero & \code{JCXZ Dest}\\
|
|
\code{JP} & jump if parity (even) & \code{JP Dest}\\
|
|
\code{JPE} & jump if parity even & \code{JPE Dest}\\
|
|
\code{JNE} & jump if not equal & \code{JNE Dest}\\
|
|
\code{JNZ} & jump if not zero & \code{JNZ Dest}\\
|
|
\code{JECXZ} & jump if ECX zero & \code{JECXZ Dest}\\
|
|
\code{JNP} & jump if not parity & \code{JNP Dest}\\
|
|
\code{JPO} & jump if parity odd & \code{JPO Dest}\\
|
|
\code{JA} & jump if above (unsigned) & \code{JA Dest}\\
|
|
\code{JAE} & jump if above or equal (unsigned) & \code{JAE Dest}\\
|
|
\code{JB} & jump if below (unsigned) & \code{JB Dest}\\
|
|
\code{JBE} & jump if below or equal (unsigned) & \code{JBE Dest}\\
|
|
\code{JNA} & jump if not above (unsigned) & \code{JNA Dest}\\
|
|
\code{JNAE} & jump if not [above or equal] (unsigned) & \code{JNAE Dest}\\
|
|
\code{JNB} & jump if not below (unsigned) & \code{JNB Dest}\\
|
|
\code{JNBE} & jump if not [below or equal] (unsigned) & \code{JNBE Dest}\\
|
|
\code{JC} & jump if carry & \code{JC Dest}\\
|
|
\code{JNC} & jump if no carry & \code{JNC Dest}\\
|
|
\code{JG} & jump if greater (signed) & \code{JG Dest}\\
|
|
\code{JGE} & jump if greater or equal (signed) & \code{JGE Dest}\\
|
|
\code{JL} & jump if less (signed) & \code{JL Dest}\\
|
|
\code{JLE} & jump if less or equal (signed) & \code{JLE Dest}\\
|
|
\code{JNG} & jump if not greater (signed) & \code{JNG Dest}\\
|
|
\code{JNGE} & jump if not [greater or equal] (signed) & \code{JNGE Dest}\\
|
|
\code{JNL} & jump if not less (signed) & \code{JNL Dest}\\
|
|
\code{JNLE} & jump if not [less or equal] (signed) & \code{JNLE Dest}\\
|
|
\code{JO} & jump if overflow & \code{JO Dest}\\
|
|
\code{JNO} & jump if not overflow & \code{JNO Dest}\\
|
|
\code{JS} & jump if sign (negative) & \code{JS Dest}\\
|
|
\code{JNS} & jump if no sign (positive) & \code{JMS Dest}\\
|
|
\end{longtable}
|
|
|
|
|
|
\subsection{Intel Syntax} % TODO rename or move
|
|
In der Vorlesung wird die \vocab{Intel Syntax} verwendet.
|
|
\begin{lstlisting}
|
|
// Reihenfolge der Operationen
|
|
cmd dest, src
|
|
// Beispiel:
|
|
mov rax, rcx // rax := rcx
|
|
|
|
// Indizierung
|
|
[base+index*scale+disp]
|
|
// Beispiel:
|
|
mov rax, [rbx] // rax := *rbx
|
|
mov rax, [rbx + 3] // rax := *(rbx + 3)
|
|
mov rax, [rbx+rcx*4h-20h] // rax := *(rbx + 0x4 * rcx - 0x20)
|
|
\end{lstlisting}
|
|
|
|
|
|
\subsubsection{Adressierungsarten}
|
|
Die meisten Befehle können ihre Operanden aus Registern, aus dem Speicher oder unmittelbar (immediate) einer Konstante entnehmen.
|
|
|
|
\begin{itemize}
|
|
\item Registeradressierung: \code{mov rbx, rdi}
|
|
\item Unmittelbare Adressierung: \code{mov rbx, 1000}
|
|
\item Direkte Adressierung: \code{mov rbx, [1000]}
|
|
\item Register-indirekte Adressierung: \code{mov rbx, [rax]}
|
|
\item Basis-Register Adressierung: \code{mov rax, [rsi+10]}
|
|
\item Allgemein: \code{mov register, segreg\footnote{Segmentregister sind nur im Real Mode relevant.}:[base + index*scale+disp]}
|
|
|
|
(\code{base, index} General-Purpose Register, \code{scale} $\in \{1,2,4,8\}$, \code{disp} beliebige Konstente)
|
|
\end{itemize}
|
|
Als General Purpose Register für Basis und Index bei indirekter Adressierung können \code{rax, rbx, rcx, rdx, rsi, rdi, r8, ..., r15} verwendet werden.
|
|
|
|
\begin{warning}
|
|
Die Adressierungsarten sind nicht beliebig miteinander kombinierbar. Z.B.~können nicht zwei Operanden mit Register- bzw. indirekter Adressierung verwendet werden.
|
|
\end{warning}
|
|
|
|
|
|
\subsubsection{Beispielprogramme}
|
|
\begin{lstlisting}[language={[x86masm]Assembler}]
|
|
; syscall "write"
|
|
%macro write 3
|
|
mov rax, 1
|
|
mov rdi, %1
|
|
mov rsi, %2
|
|
mov rdx, %3
|
|
syscall
|
|
%endmacro
|
|
|
|
SECTION .data
|
|
hello: db "Hello World!", 10
|
|
helloLen: equ $ - hello
|
|
|
|
|
|
global _start ; Fuer den Linker sichtbares Label.
|
|
|
|
SECTION .text ; Textteil des Assembler-Programms, das eigentliche Programm selbst.
|
|
_start: ; _start ist Label fuer Start des Hauptprogramms.
|
|
write 1, hello, helloLen
|
|
mov rax, 60
|
|
mov rdi, 0
|
|
syscall
|
|
\end{lstlisting}
|
|
|
|
Das Programm kann wie folgt kompiliert und ausgeführt werden:
|
|
\begin{lstlisting}[language=bash]
|
|
# Programmtext in helloworld.S speichern
|
|
|
|
# Aufruf des Assemblers
|
|
nasm -f elf64 -o helloworld.o helloworld.S
|
|
|
|
# Aufruf des Linkers
|
|
ld -o helloworld helloworld.o
|
|
|
|
# Aufruf des Linkers (clang)
|
|
ld.lld -o helloworld helloworld.o
|
|
|
|
# Ausfuehren
|
|
./helloworld
|
|
strace ./helloworld > /dev/null
|
|
gdb ./helloworld
|
|
\end{lstlisting}
|
|
|
|
|
|
\begin{example}[Summe]
|
|
~
|
|
|
|
\begin{minipage}[t]{0.33\textwidth}
|
|
\textbf{C}
|
|
\begin{lstlisting}
|
|
summe = a + b + c + d;
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\begin{minipage}[t]{0.33\textwidth}
|
|
\textbf{Assembler (konzeptuell)}
|
|
\begin{lstlisting}
|
|
summe = a;
|
|
summe += b;
|
|
summe += c;
|
|
summe += d;
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\begin{minipage}[t]{0.33\textwidth}
|
|
\textbf{Assembler (Intel Syntax)}
|
|
\begin{lstlisting}[language={[x86masm]Assembler}]
|
|
; a,b,c,d sind
|
|
; Speicherstellen
|
|
mov rax, [a]
|
|
add rax, [b]
|
|
add rax, [c]
|
|
add rax, [d]
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\end{example}
|
|
~
|
|
|
|
|
|
\begin{example}[If-Then-Else]
|
|
~
|
|
|
|
\begin{minipage}[t]{0.33\textwidth}
|
|
\textbf{C}
|
|
\begin{lstlisting}
|
|
if (a == 4711) {
|
|
// ...
|
|
} else {
|
|
// ...
|
|
}
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\begin{minipage}[t]{0.33\textwidth}
|
|
\textbf{Assembler (konzeptuell)}
|
|
\begin{lstlisting}
|
|
if(a != 4711){
|
|
goto ungleich;
|
|
}
|
|
gleich:
|
|
// ...
|
|
goto weiter;
|
|
ungleich:
|
|
// ...
|
|
weiter:
|
|
// ...
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\begin{minipage}[t]{0.33\textwidth}
|
|
\textbf{Assembler (Intel Syntax)}
|
|
\begin{lstlisting}[language={[x86masm]Assembler}]
|
|
cmp rax, 4711
|
|
jne ungleich
|
|
gleich:
|
|
; ...
|
|
jmp weiter
|
|
ungleich:
|
|
; ...
|
|
weiter:
|
|
; ...
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\end{example}
|
|
|
|
|
|
\begin{example}[Schleife]
|
|
~
|
|
|
|
\begin{minipage}[t]{0.49\textwidth}
|
|
\textbf{C}
|
|
\begin{lstlisting}
|
|
for (int i = 100; i > 0; --i) {
|
|
summe = summe + a;
|
|
}
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\begin{minipage}[t]{0.49\textwidth}
|
|
\textbf{Assembler (Intel Syntax)}
|
|
\begin{lstlisting}[language={[x86masm]Assembler}]
|
|
mov rcx, 100
|
|
schleife:
|
|
add rax, [a]
|
|
; decrement rcx and jump if rcx != 0
|
|
loop schleife
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\end{example}
|
|
|
|
|
|
|
|
\begin{example}[Schleife (ohne \code{loop}) ]
|
|
~
|
|
|
|
\begin{minipage}[t]{0.49\textwidth}
|
|
\textbf{C}
|
|
\begin{lstlisting}
|
|
for (int i = 0; i < 100; ++i) {
|
|
summe = summe + a;
|
|
}
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\begin{minipage}[t]{0.49\textwidth}
|
|
\textbf{Assembler (Intel Syntax)}
|
|
\begin{lstlisting}[language={[x86masm]Assembler}]
|
|
mov rcx, 0
|
|
schleife:
|
|
add rax, [a]
|
|
inc rcx
|
|
cmp 100, rcx
|
|
jne schleife
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\end{example}
|
|
|
|
|
|
|
|
\begin{example}[Schleife (indirekte Adressierung)]
|
|
~
|
|
|
|
\begin{minipage}[t]{0.49\textwidth}
|
|
\textbf{C}
|
|
\begin{lstlisting}
|
|
int a = 0;
|
|
for(int i = 100; i > 0; --i){
|
|
a += b[2 * i - 2];
|
|
}
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\begin{minipage}[t]{0.49\textwidth}
|
|
\textbf{Assembler (Intel Syntax)}
|
|
\begin{lstlisting}[language={[x86masm]Assembler}]
|
|
mov rcx, 100
|
|
mov rax, 0
|
|
mov rbx, array
|
|
schleife:
|
|
add rax, [rbx+rcx*2-2]
|
|
loop schleife
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\end{example}
|
|
|
|
|
|
\section{Calling Conventions}
|
|
|
|
\begin{definition}[Calling Conventions]
|
|
Wenn Assembler-Routinen aus einer Hochsprache aufgerufen werden sollen, müssen bestimmte \vocab{Calling-Conventions} eingehalten werden. Diese legen fest, wie Parameter und Rückgabewerte übergeben werden, und welche Register nach dem Aufruf noch zur Verfügung stehen.
|
|
Auch innerhalb eines Assembler-Programms macht es Sinn, sich an (eigene) Calling-Conventions zu halten.
|
|
\end{definition}
|
|
|
|
\begin{example}
|
|
~
|
|
\begin{lstlisting}[language={[x86masm]Assembler}]
|
|
SECTION .data
|
|
input1: db 0
|
|
input2: dd 0
|
|
|
|
SECTION .text
|
|
get_int:
|
|
call read_int ;jump and push address to stack
|
|
mov [rbx], rax
|
|
ret ;pop return address from stack and jump
|
|
|
|
; ...
|
|
|
|
mov rbx, input1
|
|
call get_int
|
|
mov rbx, input2
|
|
call get_int
|
|
|
|
; ...
|
|
|
|
\end{lstlisting}
|
|
\end{example}
|
|
|
|
\subsection{cdecl Calling Convention (32 bit)}
|
|
\begin{itemize}
|
|
\item Parameterübergabe über den Stack: Der Aufrufer legt Argumente auf den Stack
|
|
Die Parameter werden in umgekehrter Reihenfolge auf den Stack gelegt.
|
|
Nach dem Aufruf muss der Aufrufer die Argumente wieder vom Stack entfernen.
|
|
\item Die Unterroutine sichert den Basepointer (\code{ebp}) auf den Stack und schreibt den Stackpointer (\code{esp}) in den Basepointer
|
|
\item \vocab{Lokale Variablen} werden auf dem Stack abgelegt
|
|
\item Der Rückgabewert wird in \code{eax} übergeben (falls möglich, sonst in Spezialregister)
|
|
\item Retten von Registern: Die Register \code{ebx, esi, edi, ebp, cs, ds, ss, es}\footnote{\code{cs, ds, ss, es} sind Segmentregister} werden nicht in der Routine verändert (ggf. mit \code{push} und \code{pop} sichern).
|
|
\code{eax, ecx, edx} können bedenkenlos verwendet werden.
|
|
\end{itemize}
|
|
|
|
Der Stack wächst von oben nach unten, d.h. in Richtung kleiner werdender Adressen.
|
|
|
|
Auf den $i$-ten Parameter kann mit \code{ebp + 4 * i} zugegriffen werden, auf die $i$-te lokale Variable mit \code{ebp - 4 * i}
|
|
|
|
|
|
\begin{example}[cdecl]
|
|
~
|
|
|
|
\begin{minipage}[t]{0.49\textwidth}
|
|
\textbf{Aufrufer}
|
|
\begin{lstlisting}[language={[x86masm]Assembler}]
|
|
; call starts(3,12)
|
|
|
|
push 12
|
|
push 3
|
|
call stars
|
|
|
|
; adjust stack ("delete" arguments)
|
|
add esp, 8
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
\begin{minipage}[t]{0.49\textwidth}
|
|
\textbf{Funktion}
|
|
\begin{lstlisting}[language={[x86masm]Assembler}]
|
|
; void stars(int low, int high)
|
|
stars:
|
|
; set up stack frame
|
|
push ebp
|
|
mov ebp, esp
|
|
|
|
; save ebx (if required)
|
|
push ebx
|
|
|
|
; do something ...
|
|
|
|
; restore ebx
|
|
pop ebx
|
|
|
|
; reconstruct stack-
|
|
; and base-pointer
|
|
leave
|
|
|
|
;return
|
|
ret
|
|
\end{lstlisting}
|
|
\end{minipage}
|
|
|
|
\end{example}
|
|
|
|
\begin{table}[htpb]
|
|
\centering
|
|
\caption{Stack}
|
|
\label{tab:stack}
|
|
\begin{tabular}{cc|c}
|
|
& & \dots\\
|
|
& \code{ebp + 12} & Parameter high\\
|
|
& \code{ebp + 8} & Parameter low\\
|
|
\code{esp + 8} & \code{ebp + 4} & Rücksprungadresse\\
|
|
\code{esp + 4} & \code{ebp} & gerettetes \code{ebp} \\
|
|
\code{esp} & \code{ebp - 4} & lokale Variable \\
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
\subsection{System V AMD64 ABI Calling Convention (64 bit)}
|
|
|
|
\begin{itemize}
|
|
\item Parameterübergabe über Register \code{rdi, rsi, rdx, r8, r9}. Weitere Parameter in umgekehrter Reihenfolge auf dem Stack
|
|
\item \code{rbx, rsp, rbp, r12 - r15} werden nicht von der Funktion verändert (ggf. mit \code{push} und \code{pop} sichern).
|
|
\code{rax,rcx,rdx,rsi,rdi,r8-r11} können bedenkenlos verwendet werden.
|
|
\end{itemize}
|
|
|
|
\begin{table}[htpb]
|
|
\centering
|
|
\caption{Calling Conventions}
|
|
\label{tab:registercallingconventions}
|
|
\begin{tabular}{cccccc}
|
|
\textbf{Verwendung} & 64bit & 32bit & cdecl & System V AMD64 ABI \\
|
|
\hline
|
|
Accumulator & rax & eax & return & return \\
|
|
Allgemein & rbx & ebx & call-preserved & call-preserved \\
|
|
Counter für Schleifen & rcx & ecx & call-clobbered & Argument 4, call-clobbered\\
|
|
Datenregister (mul, div, IO) & rdx & edx & call-clobbered & Argument 3, call-clobbered\\
|
|
String-Op Source & rsi & esi & call-preserved & Argument 2, call-clobbered\\
|
|
String-Op Dest & rdi & edi & call-preserved & Argument 1, call-clobbered\\
|
|
Basepointer & rbp & ebp & call-preserved & call-preserved\\
|
|
Stackpointer & rsp & esp & call-preserved & call-preserved\\
|
|
Allg. & r8 & r8d & & Argument 5, call-clobbered \\
|
|
Allg. & r9 & r9d & & Argument 6, call-clobbered \\
|
|
Allg. (X $\in \{10,11\}$ ) & rX & rXd & & call-clobbered \\
|
|
Allg. (X $\in \{12,\ldots,15\}$ ) & rX & rXd & & call-preserved \\
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
|
|
\begin{table}[htpb]
|
|
\centering
|
|
\caption{Calling Conventions (syscall)}
|
|
\label{tab:registercallingconventionssyscall}
|
|
\begin{tabular}{cccccc}
|
|
\textbf{Verwendung} & 64bit & 32bit & \cc{int 0x80} (32 bit) & \cc{syscall} (64 bit) \\
|
|
\hline
|
|
Accumulator & rax & eax & \multicolumn{2}{c}{\color{red}syscall number / return} \\
|
|
Allgemein & rbx & ebx & Argument 1 & call-preserved \\
|
|
Counter für Schleifen & rcx & ecx & Argument 2 & {\color{red} call-clobbered}\\
|
|
Datenregister (mul, div, IO) & rdx & edx & Argument 3 & Argument 3\\
|
|
String-Op Source & rsi & esi & Argument 4 & Argument 2\\
|
|
String-Op Dest & rdi & edi & Argument 5 & Argument 1\\
|
|
Basepointer & rbp & ebp & Argument 6 & \\
|
|
Stackpointer & rsp & esp & call-preserved &\\
|
|
Allg. & r8 & r8d & & Argument 5\\
|
|
Allg. & r9 & r9d & & Argument 6\\
|
|
Allg. & r10 & r10d & & Argument 4\\
|
|
Allg. & r11 & r11d & &{\color{red}call-clobbered} \\
|
|
Allg. (X $\in \{12,\ldots,15\}$ ) & rX & rXd & & call-preserved \\
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
|
|
|
|
|
|
\begin{example}[Funktionsaufruf (64 bit)]
|
|
~
|
|
\begin{lstlisting}[language={[x86masm]Assembler}]
|
|
; void starts(int low, int high)
|
|
|
|
; call stars(3,12)
|
|
mov rdi, 3
|
|
mov rsi, 12
|
|
call stars
|
|
\end{lstlisting}
|
|
|
|
\end{example}
|
|
|
|
\section{Beispiele}
|
|
\todo{Beispiele}
|
|
|
|
\section{RISC-Architekturen}
|
|
|
|
|
|
\subsection{ARM-Assembly}
|
|
|
|
Die meisten Smartphones nutzen ARM.
|
|
Unterschiede zu x86:
|
|
\begin{itemize}
|
|
\item \vocab{RISC} statt \code{CISC}:
|
|
ARM ist ein Reduced-Instruction-Set-Computing-Prozessor. Es stehen deutlich weniger Befehle zur Verfügung, dafür aber mehr allgemein verfügbare Register als beim Complex Instruction Set Computing.
|
|
Einzelne Befehle können schneller ausgeführt werden.
|
|
\item ARM-Befehle arbeiten nur auf Registern und nutzen Load/Store-Befehle für den Speicherzugriff
|
|
\item Der ARM-Befehlssatz nutzt 31 allgemein verfügbare 64-bit-Register \code{X0} - \code{X30}
|
|
\end{itemize}
|
|
|
|
\subsection{MIPS-Assembly}
|
|
|
|
MIPS ist ebenfalls eine RISC-Architektur. Es gibt 3 Befehlsformate:
|
|
\begin{itemize}
|
|
\item R-Befehle: \code{instruction rd, rs, rt}
|
|
|
|
\code{rs} und \code{rt} sind Quell-Register, \code{rd} ist das Zielregister
|
|
\item I-Befehle: \code{instruction rt, rs, imm}
|
|
|
|
\code{rs} ist das Quellregister, \code{rt} ist das Zielregister, \code{imm} ist ein 16-bit immediate-Wert
|
|
|
|
\item J-Befehle: \code{instruction addr}
|
|
|
|
Springt an die angegebene (absolute) Adresse
|
|
|
|
\end{itemize}
|
|
|
|
\section{Speicherverwaltung}
|
|
|
|
In C muss Speicher manuell mit \code{malloc} und \code{free} verwaltet werden:
|
|
\begin{lstlisting}
|
|
int *p;
|
|
p = malloc(1024 * sizeof(int));
|
|
if(p == NULL) {/* error * /}
|
|
free(p);
|
|
\end{lstlisting}
|
|
|
|
\code{malloc} und \code{free} verwalten freien Speicher in einer zyklisch verketteten Liste.
|
|
Jedem freien Speicherblock geht ein Header voraus. Der Header enthält die Größe des freien Speicherblocks (ohne Header) und einen Zeiger auf den nächsten freien Speicherblock.
|
|
|
|
|
|
Ein Aufruf von \code{malloc} durchläuft die Liste freier Blöcke und sucht einen passenden. Eine mögliche Strategie ist first fit.
|
|
Wenn das genau passt, wird der Block aus der free-Liste entfernt. Anderenfalls wird die benötigte Menge Speicher hinten abgeschnitten und die Größe im Header entsprechend angepasst.
|
|
Wenn kein passender Block gefunden wird, wird vom Betriebssystem mehr Speicher angefordert (\code{mmap}, \code{brk}). Hieraus kann ein neuer Block erstellt werden. Der Rückgabewert ist ein Zeiger auf das erste Byte hinter dem Header.
|
|
|
|
\code{free} erhält (hoffentlich) einen Zeiger, den ein früherer Aufruf von \code{malloc} zurückgegeben hat.
|
|
Vor der Adresse liegt also ein \code{malloc}-Header mit der Größenangabe. \code{free} verwendet diese Information um einen freien Block zu generieren, freie Blöcke unmittelbar davor oder dahinter werden ggf. mit diesem Block zusammengefasst.
|
|
|
|
\todo{Beispiel aus Aufgabe einfügen}
|
|
|
|
\subsection{Typische Fehler}
|
|
|
|
\begin{itemize}
|
|
\item Es wird nicht überprüft, ob die Rückgabe \code{NULL} ist.
|
|
\item Speicher wird nicht wieder freigegeben $\leadsto$ \vocab{memory leak}
|
|
\item Ein Zeiger, der in bereits freigegebenen Speicherbereich zeigt, wird verwendet: Undefiniertes Verhalten. Ggf. ein \vocab{Segmentation Fault}, falls die Adressen dem Programm mittlerweile nicht mehr gehören.
|
|
\item Aufruf von \code{free} auf einem Zeiger, der nicht Ergebnis eines \code{malloc}-Aufrufs war: Undefiniertes Verhalten.
|
|
\end{itemize}
|
|
|
|
\begin{remark}
|
|
Das Programm \code{valgrind} oder die Option \code{-fsanitize=address} beim Kompilieren helfen beim Debuggen solcher Fehler.
|
|
|
|
\end{remark}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|