.top
mov di,num1+digits-1
mov si,num2+digits-1
mov cx, digts
mov bp, num2
dec dword [term]
jz .done
mov di,num2+digits-1
mov si,num1+digits-1
mov cx,digits
call AddNumber
Befehle wie mov
, jz
oder call
werden vom Prozessor verarbeitet.
Hochsprache -> Algorithmen optimieren -> Compiler-Optimierungen -> Assembler anpassen
Unterscheidet in verschiedene Assembler Arten/Syntaxe unter x86 z.B.: AT&T und Intel Asm
#include <stdio.h>
#define MSG "Hello World"
int main(void)
{
//Hello world
printf(MSG);
printf(" and bye\n");
return 0;
}
Normales kompilieren mit gcc: gcc -Wall example.c -o example
Anzeige der Temporären Dateien: gcc -Wall -save-temps example.c -o example
Die .i Datei zeigt die Schritte des Preprozessing. Preprozessing Datei
Also ersetzt Macros und entfernt Kommentare. Einfügen von Libarys.
Die .s Datei zeigt den generierten Assembler Code an. Assembler Datei
Die .o Datei zeigt Binäre Maschinensprache an. Danach muss dieser noch gelinkt werden. Maschinensprache Datei
Es entsteht ein fertiges Programm. Programm
Nur Assembler Output: gcc -S example.c -o example.s
Objekt File erstellen: gcc -S -c example.s -o example.o
Programm linken: gcc example.o -o example
+--------------------------------------------------------------------+
| |
| +------------------------------------------------------------+ |
| | Control Unit | |
| +----+--------------------------------------------------+----+ |
| ^ +---------+ +----------+ | |
| | | | | Clock | | |
| | | ALU | +----------+ | |
| | | | | |
| | +-----+---+ | |
| | ^ | |
| | v | |
| | +-------+------+ | |
| | | | | |
| | | Register | | |
| | | | | |
| | +-------+------+ | |
| | ^ | |
| | | | |
| | v v |
| +----+-------+ +------+--------+ +------+----+ |
| | | | | | | |
| | Input +---->+ Speicher +------------>+ Output | |
| | | | | | | |
| +------------+ +---------------+ +-----------+ |
| |
+--------------------------------------------------------------------+
Ein Programm, was sich selbst beendet:
movl $0, %ebx
movl $1, %eax
int 0x80
ebx
und eax
sind Register. Dort werden Variabeln reingepackt.
Das int $0x80
sagt dem Linux Kernel, es soll den Syscall der in %eax
steht ausführen. In diesem Fall der erste, welcher für Schließen steht.
+---------------+
0x00 | |
+---------------+
... | |
+---------------+
0x10 | |
+-------+-------+
0x11 | Dword | Dword |
+-------+-------+
| LWord |
+---------------+
| |
+---------------+
mov (%ebx), %eax
1 Zeile: lädt Inhalt aus der Adresse (%ebx
) in Register %eax
.
esi
= für String Operationen
movb $0, %eax
movb
-> move 8 bit
movvw
-> move 16 bit
movl
-> move 32 bit
movq
-> move 64 bit
Speicher in der CPU, dient als Zwischenspeicher. Ist zwischen Ram und Registern zu sehen.
esp
-> Stak Pointer
Verschiebt eax
auf den Stack:
movq $0, %eax
pushq %eax
Stack Pointer wächst, zeigt auf die letzte Zahl im Stack.
Lesen vom Stack und in %eax
reinschreiben:
popq %eax
Main Prozedur:
main:
...
call myporc
implicit:
...
myporc:
...
ret
eip
-> Instruktion Pointer
+---------------+
eip +---->+ .... |
+---------------+
| call myproc |
+---------------+
| ... |
+---------------+
| myproc |
+---------------+
| ret |
+---------------+
| |
+---------------+
| |
+---------------+
esp +---->+ |
+---------------+
Instrution Pointer geht nach unten. Der Stack Pointer geht nach oben. Der Instruktion Pointer soll die Funktion myproc aufrufen. Bevor sie das tut, wird die Adresse+1 in den Stack geschrieben (implicit). Um nachher wieder, zurück zu kehren.
+---------------+
| .... |
+---------------+
| call myproc |
+---------------+
| ... |
+---------------+
eip +---->+ myproc |
+---------------+
| ret |
+---------------+
| |
+---------------+
| |
+---------------+
esp +---->+ implicit |
+---------------+
Am Ende der Prozedur trifft der Instruktion Pointer (eip
) auf ret
, das heißt, er soll da weiter machen, wo er zuvor aufgehört hat. Wert implicit
wird in vom Stack gepopt. Und in eip
geschrieben.
Damit geht er wieder nach oben.
+---------------+
| .... |
+---------------+
| call myproc |
+---------------+
eip +---->+ ... |
+---------------+
| myproc |
+---------------+
| ret |
+---------------+
| |
+---------------+
| |
+---------------+
esp +---->+ |
+---------------+
Wichtig ist, dass hier jetzt richtig terminiert wird.
.text
.data
.global main
main:
movl $4, %eax
movl $1, %ebx
movl $msg, %ecx
movl $len, %edx
int $0x80
movl $0, %ebx
movl $1, %eax
int $0x80
msg:
.ascii "Hello World."
len = . - msg
.text
, .data
und .global main
sind Sections.
Wichtig erstmal ist .global main
dieses sagt, das wir ab main
starten wollen.
Mit int $0x80
wird ein Syscall aufgerufen, die Parameter für diesen sind in den Registern:
eax
-> Syscall (4 für write)
ebx
-> Output (1 für stout also Commandline)
ecx
-> Text (msg)
edx
-> Länge der Nachricht (len)
Um eine Ausgabe zu erzeugen, brauchen wir den vierten Syscall, darum wird die 4 in eax
gepackt.
Kompilieren:
gcc -c hello.s -o hello.o
gcc -no-pie hello.o -o hello
.text
.data
.global main
main:
movl $4, %eax
movl $1, %ebx
movl $one, %ecx
movl $onelen, %edx
int $0x80
movl $0, %ebx
movl $1, %eax
int $0x80
one:
.ascii "1"
onelen = . - one
cmp $3, %esi # vergleich 3 mit esi
jne notequal # Spring zu notequal wenn ungleich
Kompletter Assembler Quellcode Datei
Mit cmp
also Compare lassen sich Dinge vergleichen. Mit Jumps lässt sich dann an die nächste stelle springen.
jmp
-> Jump
je
-> Equal
jne
-> Not Equal
jg
-> Greater
jge
-> Greater or Equal
jl
-> Less
jle
-> Less or Equal
ja
-> Above, ignoriert Vorzeichen
jae
-> Above or Equal
jb
-> Below
jbe
-> Below or Equal
jo
-> Overflow (Überlauf von Plus zu Minus)
jno
-> No Overflow
jz
-> Zero
jnz
-> Not Zero
js
-> Signed
jns
-> Not Signed
add $3, %esi # Zahlen auf Register addieren
add %eax, %esi # eax auf esi addieren
sub %eax, %esi
Die CPU kann eigentlich kein subtrahieren, darum wird ein kleiner Trick angewendet.
neg %eax
add %eax, %esi
eax
negiert und dann auf esi
addiert.
Der mul
Befehl multipliziert immer aus eax
. Das Ergebnis wird dann wieder in eax
hineingeschrieben. (Wenn zu groß dann noch in edx
)
movl $1, %eax # 1 in eax
mul %esi # multipliziert eax und esi
movl %eax, %esi # zurück moven
Ermöglicht multiplizieren mit
imul $3, %esi # 3 multiplizieren mit esi
Wie bei der Multiplikation wird immer mit eax
gerechnet. Das Register edx
wird bei der Berechnung mit verwendet und muss vorher “gesäubert” werden.
eax
-> Quotient
edx
-> Rest
movl $0, %edx
div %esi # Division %edx:%eax / %esi
movl %eax, %esi
Schleife bis 5. Vergleich mit ecx
, bis dieser gleich Null ist. ecx
wird dabei um ein verringert.
movl $5, %ecx
Schleife:
add $1, %esi
loop Schleife
loop
-> Schleife, wird ausgeführt, bis ecx
= 0, verringert ecx
immer um 1
loope
-> equals (ob das Zero Bit auf Null gesetzt wurde, also die letzte Aufruf Null ergeben hat.) (ecx
muss größer Null sein.)
loopz
-> genauso wie loope
UND
auf zwei Registern:
movl $0xFFFF, %esi
movl $0x0, %ecx
andl %ecx, %esx # Gespeichert in esx
orl
-> OR
xorl
-> XOR
Schift Links um eins:
movl $1, %esi #000...0001 in %esi
shll $1, %esi #000...0010 in %esi
shrl
-> Rechts (das l für dobbel word.)
Bei Prozeduren, die nicht der Reihe aufgerufen werden, werden Stack Frames gebraucht.
main:
...
call poc2
proc1:
...
proc2:
call proc1
ret
esp-->+---------------+
| vars proc1 |
frame pointer-->+---------------+
| return addr |
+---------------+
| params proc1 |
+---------------+
| Vars proc2 |
+---------------+
| return addr |
+---------------+
| params proc2 |
+---------------+
frame pointer
zeigt die Adresse auf die zurück gesprungen werden soll.
Der Frame besteht immer aus den 3 Angaben von variabeln
, return adresse
und Parametern
.
.file "example.c" # Debugger kann nachvollziehen aus welcher Datei das kommt.
.text # Könnte Code beinhalten.
.section .rodata # Read Only Data
.LC0: #
.string "Hello World" # Nur lesbar (Darf nicht verändert werden.)
.LC1:
.string " and bye"
.text
.globl main # .globl wird das Programm aufgerufen
.type main, @function # Funktion main die Aufgerufen werden kann.
main:
.LFB0: # Local Function Begin (Nummer)
.cfi_startproc # Starten einer Prozedur (checken das ein Frame Pointer vorhanden ist)
pushq %rbp
.cfi_def_cfa_offset 16 # Canonical Frame Address Pointer CFA (zeigt auf Frame vor dem akktuellen Frame)
.cfi_offset 6, -16 # Frame Pointer
movq %rsp, %rbp
.cfi_def_cfa_register 6 # CFA = Register Nummer 6
leaq .LC0(%rip), %rdi # LC0 Auslesen und in rdi verschieben
movl $0, %eax
call printf@PLT # printf aufrufen aus procedure linkage table (PLT)
leaq .LC1(%rip), %rdi # LC1 in rdi
call puts@PLT # Put aufrufen (weil Output schon offen ist, durch printf)
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc # Debbuger info
.LFE0: # Local Function End (Nummer)
.size main, .-main
.ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
#include <stdio.h>
int main (void)
{
int num = 24, output;
asm("movl %1, %%ebx;" // Prozentzeichen doppelt um zugriff zu erhalten
"movl %%ebx, %0 ;"
: "=r" (output) // OUTPUT
: "r" (num) // INPUT
:"%ebx" // USED REGISTERS
);
printf("%d\n", output);
return 0;
}