Programmieren x86-Assembler
Skript
Mein Skript gibt es
hier.
Links
Tips & Lieblingsfehler
- Unterscheide bei Operanden:
- Registername ohne [ ]: Inhalt des Registers. Kein Speicher-Zugriff!
- Registername in [ ] (z.B. [edi]): Inhalt der Speicherstelle, auf die das Register zeigt (Register muß Adresse bzw. Pointer enthalten).
- Label ohne [ ]: Wert des Labels, d.h. eine Konstante, nämlich die Adresse der mit dem Label bezeichneten Variable. Kein Speicher-Zugriff!
Analog für Ausdrücke, mystr+1024 liefert beispielsweise die Adresse der Speicherstelle 1024 Bytes hinter jenem Byte, auf das das Label mystr zeigt.
- Label in [ ] (z.B. [myvar]): Inhalt der durch das Label bezeichneten Speicherstelle bzw. Variable.
Auch hier sind Ausdrücke möglich: byte [mystr+2] liefert den Inhalt des dritten Bytes jenes Speicherbereiches, der beim Label mystr beginnt.
- Hat ein Befehl, der Daten bewegt oder verarbeitet, kein Register als Operand, so braucht er eine Längenangabe bei einem seiner Operanden.
Beispiele:
- Befehle mit einem Speicher-Operanden und einer Konstante, z.B. mov byte [edi], 0x0a oder add dword [myvar], 12345.
- Befehle mit einem Speicher-Operanden, beispielsweise inc dword [myvar].
- push- und pop-Befehle mit Speicher-Operanden oder Konstanten, z.B. push dword '9' oder pop dword [edi].
- Es gibt keine Befehle, die zwei Operanden im Speicher unterstützen (außer den String-Befehlen movsb/...): imul [myvar], [myvar], 12345 ist genauso unmöglich wie mov [myvar], [esi].
- Der Assembler kann konstante Ausdrücke (d.h. Ausdrücke, bei denen alle Operanden Konstanten sind) berechnen, und Labels sind Konstanten (ein Label steht für eine Adresse, und Adressen sind Zahlen)!
Man kann daher überall dort, wo man eine Zahl oder ein Label verwenden darf, auch eine Rechnung mit Zahlen und Labels hinschreiben, man braucht dafür keine eigenenen arithmetischen Befehle!
Beispiele: cmp edi, mystr+1024 oder mov esi, strend-1
- Man unterscheide Ziffern als einstellige Zahlen (ohne Anführungszeichen: 9 ist die Zahl 9) und den ASCII-Wert von Ziffern (mit Anführungszeichen: '9' liefert die Zahl 57).
- Der linke Operand eines mov oder eines arithmetischen Befehls kann keine Konstante und daher auch kein Label sein: mov myvar, eax ist ebenso Unfug wie add dword myvar, 12345.
Wenn, dann muß es ein Speicher-Operand sein: mov [myvar], eax oder add dword [myvar], 12345.
- Wenn man ein 8-Bit-Register lädt (z.B. al mit lodsb oder mov al, 'x'), so bleiben die restlichen 3 Bytes des 32-Bit-Registers unverändert (und daher unvorhersehbar, wenn man sie vorher nie initialisiert hat)! Nachfolgendes Arbeiten mit dem ganzen Register (z.B. cmp eax, 0 oder push eax) baut daher Mist!
Will man einen "kleinen" Operanden in ein "größeres" Register laden und dabei auch die "unbeteiligten" Bytes des Registers auf 0 setzen, braucht man die movzx- oder movsx-Befehle, oder im Fall von eax die Befehle cbw und cwde.
- mov und arithmetische Befehle arbeiten nur auf gleichgroßen Operanden (Ausnahme: 32 Bit Operand und 8 Bit Konstante): Befehle der Art mov edx, al gibt es nicht!
- Der Stack arbeitet im 32-Bit-Mode ausschließlich mit 32-Bit-Objekten (oder 64 Bit usw.), der Stack-Pointer muß immer ein Vielfaches von 4 sein, sonst gibt es einen Absturz! Auch char-Argumente müssen daher als int-Werte gepusht werden, also push dword '0', nicht push byte '0'!
- Laut C-Aufrufskonvention werden die Argumente von rechts nach links gepusht (das vordeste Argument zuletzt)!
- Nach einem call einer Funktion mit Argumenten nicht auf das Aufräumen des Stacks vergessen: add esp, Anzahl_der_Bytes
- Der Returnwert einer Funktion befindet sich nach dem call im Register eax!
- Unterscheide:
resb, resw, resd, ...:
- Reservieren uninitialisierten Speicher (mit 0 vorbelegt).
- Gehören in die Section .bss.
- Haben als Operand die Anzahl der zu reservierenden Bytes/... .
db, dw, dd, ...:
- Definieren ein Byte/... mit vorgegebenem Initialisierungswert.
- Gehören in die Section .data.
- Haben als Operand den Initialisierungswert des Bytes/... .
- Wenn man mehrere Bytes/... braucht, muß man entsprechend viele Operanden angeben oder times verwenden.
equ:
- Belegt überhaupt keinen Speicher, hat daher auch keine Größe und braucht keine bestimmte Section.
- Weist dem Label den Wert des Operanden (d.h. eine Integer-Konstante) zu, nicht dessen Adresse (weil ein equ ja gar keinen Speicher und daher auch keine Adresse hat!).
- Ist vergleichbar mit dem #define in C: atchar equ '@' entspricht in etwa #define atchar '@'
- Die Platzangabe bei einem resb erfolgt in Bytes, nicht in Bits. Für eine int-Variable braucht man also resb 4, nicht resb 32. Noch besser wäre resd 1!
- Nach einem Vergleich von vorzeichenlosen Zahlen (in C: unsigned int) oder Pointern springt man mit above und below, nach einem Vergleich von "normalen" Integers mit greater und less.
- Bei Verwendung von String-Befehlen: Nicht auf das cld vergessen!!!
- Vor einer Division: edx auf Null setzen (bei vorzeichenlosen Zahlen, mit xor edx, edx) oder mit der Vorzeichenerweiterung von eax füllen (bei Zahlen mit Vorzeichen, mit cdq).
- Nach einer Division: Quotient ist in eax, Rest ist in edx!
- Multiplikationen mit Hilfe von lea sind auf jene Faktoren beschränkt, die von den Adressierungsarten unterstützt werden: *1 *2 *4 *8
- Bei einem Assembler-Hauptprogramm, das mit _start beginnt, braucht man keine Register sichern (die sind ohnehin alle 0, und es gibt keine aufrufende Funktion, die sie nachher noch braucht).
Ein enter ist sinnvoll, aber ebenfalls nicht notwendig.