Intel-Schreibweise wie NASM – mit den Bytes, die daraus werden.
Asmonite baut ein Programm Byte für Byte aus dem Quelltext. Die Schreibweise ist die vertraute: ; für Kommentare, mov eax, [ebx+ecx*4+8], Labels, db und dd. Diese Seite zeigt, was sie kann, und neben jedem Beispiel die Bytes, die der Assembler schreibt (sie stammen aus dem echten Assembler). Wo er von NASM abweicht, steht es dabei.
Gezeigt wird x86 (32 Bit, .arch i386). Für x64, ARM Thumb-2 und ARM64 gilt dieselbe Zeilenform und dieselbe Rechnung; ihre Befehle erklärt F1 im Programm.
Höchstens ein Befehl je Zeile. Alles ist optional.
start:Label (der Doppelpunkt ist freiwillig)movBefehleax, [ebx+8]Operanden, durch Komma getrennt; copyKommentar (nur ;)start: mov eax, [ebx+8] ; copy
add eax, 1
jmp start
8B 43 08 83 C0 01 EB F8
Ein Label ist die Adresse der Stelle, an der es steht. Groß- und Kleinschreibung bei Labels ist egal (L1 und l1 sind dasselbe Label). Befehle schreibst du klein oder groß, aber ein Befehl mit großem ersten Buchstaben wählt die zweite Kodierung (siehe unten).
Wo eine Zahl steht, darf gerechnet werden: in Operanden, bei db, dw, dd, dq, resb, align, org.
Operatoren in dieser Rangfolge, die stärksten zuerst: Vorzeichen und ~; * / %; + -; << >>; &; ^; |. Klammern gehen immer. Zahlen: dezimal, 0x1F, 1Fh, 101b, 0b101 und ein Zeichen in einfachen Anführungszeichen ('A').
| Ausdruck | Wert | Anmerkung |
|---|---|---|
4*16+1 | 65 (0x41) | Punkt vor Strich |
(2+3)*4 | 20 (0x14) | Klammern |
100/7 | 14 (0xE) | ganzzahlig, mit Vorzeichen |
100%7 | 2 | der Rest |
1<<5|3 | 35 (0x23) | << bindet stärker als | |
0x10^0x11 | 1 | exklusiv-oder |
~0xF & 0xFF | 240 (0xF0) | bitweises Nicht, dann Und |
-5+8 | 3 | Vorzeichen |
'A'+1 | 66 (0x42) | ein Zeichen in Anführungszeichen ist eine Zahl |
1Fh | 31 (0x1F) | hexadezimal mit h |
101b | 5 | binär mit b |
0b101 | 5 | binär mit 0b |
name equ ausdruck gibt einer Zahl einen Namen (die Zeile schreibt keine Bytes; die Konstante darf auch hinter ihrer Verwendung stehen). $ ist die Adresse der Zeile, $$ der Anfang des Abschnitts. Der Abstand zweier Labels ist eine Zahl, also eine Größe: end-start, $-msg.
size equ 4*16
mov eax, size+1
msg: db 'hi', 0
len equ $-msg
mov al, len
start: mov ecx, end-start
nop
end:
B8 41 00 00 00 68 69 00 B0 03 B9 06
00 00 00 90
Ein Abstand braucht die Adressen; der Assembler läuft deshalb mehrmals, bis sie gleich bleiben (das merkst du nicht). Nicht möglich sind eine Zahl hinter einem Label bei Sprung und Aufruf (jmp start+4) und die Division durch null – beides ist ein Fehler mit der Zeilennummer.
Register, Zahlen, Labels und Speicher in eckigen Klammern: [Basis + Index*Faktor + Zahl].
| Du schreibst | Bytes | Anmerkung |
|---|---|---|
mov eax, [ebx] | 8B 03 | ein Register |
mov eax, [ebx+8] | 8B 43 08 | Register plus Zahl |
mov eax, [ebx+ecx*4+8] | 8B 44 8B 08 | Basis, Index mal Faktor (1, 2, 4, 8), Zahl |
mov eax, [ebx+2*4] | 8B 43 08 | der konstante Teil wird gerechnet: wie [ebx+8] |
mov eax, [ebp-4] | 8B 45 FC | ein Minus setzt die Adresse fort |
mov byte [ebx], 1 | C6 03 01 | Größe des Zugriffs: byte, word, dword |
mov word [ebx], 1 | 66 C7 03 01 00 | wie oben, 16 Bit: Präfix 66 |
mov eax, [es:ebx] | 26 8B 03 | Segment vor der Adresse |
Ein Label allein (mov eax, label) ist seine Adresse als Zahl, in Klammern (mov eax, [label]) der Speicher dort. label+4 und label-4 sind Adressen, auch als Wert und in dd. Als Adresse gilt die virtuelle Adresse im geladenen Programm (Ladeadresse plus Lage), nicht der Abstand vom Anfang der Datei.
Ein Label ist das Ziel; die Länge des Abstands sucht der Assembler selbst.
| Du schreibst | Bytes | Anmerkung |
|---|---|---|
l: jmp l | EB FE | ohne Größe nimmt der Assembler die kürzeste Form |
l: jmp near l | E9 FB FF FF FF | near: die lange Form (32 Bit Abstand) |
l: jz short l | 74 FE | short: ein Byte Abstand |
l: nop jz l | 90 74 FD | ein bedingter Sprung nach hinten |
Anders als bei NASM ist eine Zahl hinter jmp der Abstand und nicht die Zieladresse: jmp 5 ist EB 05. Mit Vorzeichen geht es auch: jmp short -2, call near -5. loop und jecxz brauchen short und ein Label oder einen Abstand.
Was im Programm als Bytes stehen soll.
| Du schreibst | Bytes | Anmerkung |
|---|---|---|
db 'AB', 0, 10 | 41 42 00 0A | Bytes; Text in einfachen Anführungszeichen |
dw 0x1234, 1+1 | 34 12 02 00 | Wörter (2 Bytes, niederwertiges Byte zuerst) |
dd 1, -1 | 01 00 00 00 FF FF FF FF | Doppelwörter (4 Bytes) |
dq 1 | 01 00 00 00 00 00 00 00 | vier Wörter: 8 Bytes |
du 'Hi' | 48 00 69 00 | Text als UTF-16: jedes Zeichen 2 Bytes |
nop align 4 nop | 90 00 00 00 90 | align füllt mit 00 bis zu einem Vielfachen |
org 4 nop | 00 00 00 00 90 | org füllt mit 00 bis zur Position in der Datei |
resb 4 nop | 00 00 00 00 90 | resb: vier Bytes Platz |
Ein Label vor einer Datenzeile (msg db 'hi', 0, mit oder ohne Doppelpunkt) ist die Adresse der Daten. Mit .format pe darf resb nur am Ende eines Abschnitts stehen: der Platz steht nicht in der Datei, sondern wird beim Laden angelegt.
Direktiven am Anfang sagen, was der Assembler schreibt. Sie stehen vor dem ersten Befehl.
Der Assembler schreibt Kopf, Abschnitte und die Importtabelle selbst; du schreibst Code, Daten und Importe.
.format pe console
.arch i386
.import msvcrt.dll cdecl printf
.import kernel32.dll ExitProcess
.code
start:
call [printf](text)
call [ExitProcess](0)
.data
text: db 'Hello', 10, 0
Mit .format binary stehen die Bytes so, wie du sie schreibst: eine .COM-Datei, ein Bootsektor oder ein selbst geschriebener Kopf.
.format binary
.output hello.com
.arch x86_real
section 0x100
start:
mov ah, 0x09
mov dx, msg
int 0x21
mov ax, 0x4C00
int 0x21
msg: db 'Hello!', 13, 10, '$'
| Direktive | Bedeutung |
|---|---|
.arch i386 | Prozessor: i386 (32 Bit), x86_real (16 Bit, DOS), x64, thumb (ARM Thumb-2), arm64 |
.format pe [console|gui] | schreibt ein Windows-Programm; .format binary die Bytes, wie sie stehen |
.output name | der Name der Datei, die geschrieben wird (neben dem Quelltext) |
.code, .data | bei pe: die folgenden Zeilen sind Programm oder Daten |
.import dll name, name | importierte Funktionen; der Name ist ein Label der Importtabelle: call [name]. Bei C-Funktionen cdecl dazuschreiben (siehe func) |
.entry label, .imagebase n | Einsprungpunkt, Ladeadresse |
section n, segment n, org n | bei binary: ein Abschnitt mit dieser virtuellen Adresse, ein DOS-Segment, Auffüllen bis zur Dateiposition |
Die Schreibweise sieht aus wie NASM, der Assembler ist aber nicht NASM-kompatibel. Das Wichtigste:
| Du schreibst | Bytes | Anmerkung |
|---|---|---|
mov eax, ebx | 8B C3 | kleingeschrieben: die erste Kodierung |
MOV eax, ebx | 89 D8 | der Befehl mit Großbuchstaben am Anfang: die zweite Kodierung, wo es zwei gibt |
mul eax, ebx | F7 E3 | der Akkumulator wird mitgeschrieben (mul ebx geht nicht) |
push byte 5 | 6A 05 | byte vor der Zahl wählt die kurze Form |
mov ax, 5 | 66 B8 05 00 | ohne .arch ist es 32 Bit: 16-Bit-Register brauchen das Präfix 66 |
| NASM | Asmonite |
|---|---|
bits 16, section .text | .arch x86_real; section <Zahl> (der Name zählt nicht) |
org 0x100 setzt die Ladeadresse | org füllt die Datei bis zu dieser Position mit 00 auf |
align 4 füllt Code mit nop | align füllt immer mit 00 |
times, incbin, Makros, %define | gibt es nicht; es gibt keinen Präprozessor |
global, extern | gibt es nicht; Importe: .import |
| Labels unterscheiden Groß und Klein | Labels nicht |
.lokal gilt bis zum nächsten Label | .l ist ein gewöhnliches Label, zweimal dasselbe ist ein Fehler |
db "AB" | nur einfache Anführungszeichen: db 'AB' |
dd label ist der Abstand vom Anfang | dd label ist die virtuelle Adresse; rva label der Abstand |
Fehler bei db 256: nur Warnung | db 256 ist ein Fehler |
Noch nicht dabei sind die x87-FPU, MMX/SSE und einige Systembefehle. Die Hilfe (F1 auf einer Zeile) in beiden Programmen erklärt jeden Befehl, den der Assembler kennt, und jede Direktive.
func schreibt den Stackframe, call Name(…) legt die Argumente ab, mit stdcall und cdecl.
F1 auf einer Zeile erklärt den Befehl, die Windows-Funktion, die Direktive und die Fehlermeldung.
Im ZIP liegen Beispiele für Windows, DOS und ARM. In 60 Sekunden vom Download zum ersten Haltepunkt.