Assembler – Grundlagen

Assembler Code ist ein in Assemblersprache (ASM) geschriebener Code, der von einem Prozessor fast direkt ausgeführt werden kann.
ASM ist eine eigene Programmiersprache, die sehr niedrig ist. Niedrig bedeutet, dass sie nah an Maschinencode, den der Prozessor tatsächlich ausführt, gelegen ist.
Assemblys müssen nicht erst, wie z.B. C-Programme, kompiliert werden! Sie werden nur Assembliert, es ist nur eine Übersetzung von ASM zu Maschinensprache.
Daher kann jedes Programm, welches kompiliert vorliegt, in Assembler-Code umgewandelt werden.

HelloWorld in ASM für Linux

Ein einfaches HelloWorld Programm für x86/64 Prozessoren unter Linux in ASM

ASM ist somit (fast) das niedrigste Level, auf der ein Prozessor programmiert werden kann. Warum das so hilfreich ist?
Man kann Programme direkt für eine CPU „maßgeschneidert“ schreiben. Das ist jedoch recht selten, viel häufiger wird der umgekehrte Weg genutzt:
Wir können jedes beliebige Programm in Assembler-Code übersetzen, und somit analysieren. Das hilft ungemein beim Reverse Engeneering, beim Analysieren von Viren und Würmern, aber auch beim Schreiben von Viren, Würmern und Exploits. (Wer daran interessiert ist: Ich werde nach der DCPU-Reihe auch eine Exploit-Reihe anfangen)
Jedoch können unterschiedliche Prozessoren auch unterschiedliche Maschinenbefehle haben, wodurch ein Assembly-Programm nicht auf jedem Prozessor läuft. Man Programmiert also immer für bestimmte Maschinen. (Nicht wie beispielsweise in Java, wo der Programmcode erst in Bytecode umgewandelt, und erst beim Ausführen für die jeweilige CPU Kompiliert und Assembliert wird.)
Die wesentlichen Opcodes der DCPU-16 in 0x10c stehen schon fest, sie sind einsehbar unter http://0x10c.net/doc/dcpu-16.txt
Es gibt 16 Opcodes. Jeder Opcode ist durch eine Zahl identifizierbar.
Wird dem Prozessor die Zahl 2 (0x2) übermittelt, weiß er, er soll eine Addition durchführen.
Die nötigen Schritte dazu übernimmt der Prozessor nun selbst, er erledigt seine Aufgabe.

Ich will nicht zu lange herum reden, wir fangen sofort mit einem Programm für die DCPU-16 an.
Da die meisten Spezifikationen schon öffentlich bekannt sind, sind auch schon einige Emulatoren erhältlich, auf denen wir Programme testen können.
Für den Anfang empfehle ich dcpubin.com

Wenn wir die Seite aufrufen, sehen wir schon einige Sachen im rechten Textfeld:
Register A-J
PC, SP, O(V)
Die Step-Anzahl
Und den RAM

In den Registern A-J können Werte gespeichert werden. A-J sind Speicher in der CPU selbst, nicht im RAM. Jedes Register kann 16 Bits Daten speichern.
PC ist der ProgramCounter
SP der StackPointer
O gibt an, ob bei einer Operation ein Overflow entstanden ist

Wir müssen noch nicht alles davon verstehen, wir schreiben einfach blind drauf los:

SET A, 10
SET B, 20
SET [0x2000], 3
MUL A, [0x2000]
ADD B, A

Assemblieren wir dies nun mit dem Button „ASSEMBLE“ über dem Eingabefeld, erhalten wir diesen Output:

a801  0000  SET A 10
d011  0001  SET B 20
8de1  0002  SET [0x2000] 3
2000
7804  0004  MUL A [0x2000]
2000
0012  0006  ADD B A

Dies ist das gleiche wie unser Assembly Code oben, nur in Opcode, also Zahlenform.
Drücken wir nun LOAD, wird unser Code in den RAM geladen:

= RAM: =

0000: [a801]  d011   8de1   2000   7804   2000   0012   0000

Das sind unsere Befehle. Wir können nun schrittweise durchgehen (STEP), oder das komplette Programm durchlaufen lassen. Wir lassen es erst einmal komplett durchlaufen (RUN), und analysieren dann das Resultat:

= REGISTERS: =
A:  001e
B:  0032
C:  0000
X:  0000
Y:  0000
Z:  0000
I:  0000
J:  0000

PC: [0007]
SP: *0000*
OV:  0000

STEP: 5

= RAM: =

0000: *a801*  d011   8de1   2000   7804   2000   0012  [0000] 
2000:  0003   0000   0000   0000   0000   0000   0000   0000

Zunächst analysieren wir die Register:
In A haben wir den Wert 0x1e, in B 0x32. Warum?
SET A, 10 weist dem Speicher in Register A den Wert 10d zu. Also 0xA
SET B, 20 bewirkt dass der Wert an Stelle B 0x14 wird (20d).
SET [0x2000], 3 verfährt auch nach dem Prinzip. Nur wird hier kein Register genutzt, sondern eine Speicheradresse. [0x2000] Steht für den Wert des Speichers an der Stelle 0x2000. Zu sehen ist dies im RAM: „2000: 0003“ – Hier sieht man, dass im Block 0x2000 der wert 0x0003 gespeichert wurde.
MUL A, [0x2000] Multipliziert den Wert der Stelle A mit dem Wert der Stelle 0x2000, also 0xA(=10d) * 0x3(=3d). Das Ergebnis, 1e(=30d) wird an der Stelle A gespeichert.
ADD B, A Addiert die Werte an den Stellen B 0x14(=20d) und A 0x1e(=30d), und Speichert das Ergebnis 0x32(=50d) in B.

Wir können den Emulator, Also CPU und RAM nun leeren (RESET), das Programm laden (LOAD), und Schrittweise durch das Programm gehen (STEP):

STEP PC A B [0x2000]
LOAD 0x0000 0x0000 0x0000 0x0000
0 0x0000 0x000A 0x0000 0x0000
1 0x0001 0x000A 0x0014 0x0000
2 0x0004 0x000A 0x0014 0x0003
3 0x0006 0x001e 0x0014 0x0003
4 0x0007 0x001e 0x0032 0x0003

Wir sehen nun auch, STEP wird bei jedem Schritt um 1 Erhöht (logisch), der PC zeigt immer auf die Adresse des Befehls, der als nächstes ausgeführt wird.
Dies war ein sehr einfaches, primitives Programm. Linearer Ablauf, einen Nutzen hat dieses Programm nicht wirklich… Daher beschäftigen wir uns im nächsten Artikel mit Schleifen, Konditionen und erweiterter ASM Programmierung.
Bis dann könnt ihr ja schon etwas üben und auf dcpubin.com herumspielen.
Ihr könnt übrigens auch das Programm aus dem Artikel aufrufen.

MfG
Damon

Dieser Eintrag wurde veröffentlicht in 0x10c und getagged , , .
Bookmarken: Permanent-Link Schreibe einen Kommentar oder hinterlasse einen Trackback: Trackback-URL.
Achtung: Wordpress interpretiert bestimmte Zeichenfolgen als Markup und verändert diese. Nutzt für Programmcode lieber Gist oder PasteBin-Services und verlinkt die Code-Schnipsel.

Post a Comment

Sie müssen angemeldet sein, um kommentieren zu können.