4 Minuten
Einfaches „hello, world“ Assembler-Programm unter Mac OS X 10.9 und dem NASM Assembler
Die folgende hello.asm Datei erklärt in den Kommentaren das Wesentliche, um den Quellcode zu verstehen. Unter Mac OS X kann man den Code mit folgenden Befehlen übersetzen, linken und ausführen.
Kompilierung
$ nasm -f macho hello.asm
$ ld -static -o hello -e teststart hello.o
$ ./hello
Assembler Code nasm
; nasm -f macho hello.asm
; ld -static -o hello -e teststart hello.o
section .data
testmsg db "hello, world!", 0xa ; String mit Zeilenumbruch
testlen equ $-testmsg ; Laenge des Strings in Bytes
section .text
global teststart ; Macht die Hauptfunktion extern sichtbar
teststart:
; TEXT AUSGEBEN MIT SYSTEMCALL WRITE
; write(int fildes, const void *buf, size_t nbyte);
; Argumente in umgekehrter Reihenfolge auf den Stack legen
; Hinweis der Stack waechst von oben nach unten also von 0xFFFF... nach 0x...0000
push dword testlen ; nbyte - Laenge des Strings
push dword testmsg ; *buf - Pointer/Adresse auf den String
push dword 1 ; fildes - Filedescriptor. In welche Datei so geschrieben
; werden. 1 = stdout
; SYSTEMCALL WRITE AUSFUEHREN
mov eax, 0x4 ; Systemcall write(...) hat Nr. 4
sub esp, 4 ; Mac OS X (BSD) braucht 4 Bytes weitern Platz auf dem Stack
int 0x80 ; Interrupt 0x80 löst einen Kernel/Systemcall Sprung aus
; STACK AUFRAEUMEN - Argumente werde nicht mehr gebraucht
add esp, 16 ; 3 Argumente * 4 Bytes (dword) gross + 4 Bytes extra
; Platz = 16 Bytes
; PROGRAMM BEENDEN
; exit(int status)
push dword 0 ; Exitstatus 0 (kein Fehler) als Argument auf den Stack legen
; SYSTEMCALL EXIT AUSFUEHREN
mov eax, 0x1 ; Systemcall exit(...) hat Nr. 1
sub esp, 4 ; Mac OS X (BSD) braucht 4 Bytes weitern Platz auf dem Stack
int 0x80 ; Systemcall machen
Beschreibung
testmsg db "hello, world!", 0xa ; String mit Zeilenumbruch
Mit der Pseudo-Instruktion db
kann man NASM anweisen, Bytes in die .o Datei
zu schreiben. Da die ASCII Charakter genau 1 Byte (8 Bit) groß sind, verwendet man den Pseudo – Befehl db
. Pseudo-Befehl heißt es deswegen, da nicht der Prozessor diesen Befehl ausführen wird, sondern es „nur“ eine Anweisung an den Assembler ist, was er mit den nachstehenden Daten machen soll. Statt alle Charakter Bytes in hexadezimaler Schreibweise mit Kommata aufzuführen, kann man diese auch in Anführungszeichen setzen. Das Byte/Zeichen 0xa
für den Zeilenumbruch, lässt sich aber weiterhin einfacher in Hex-Schreibweise hinzufügen.
Mit testmsg
benennen wir die Adresse des ersten Bytes/Zeichen der Zeichenkette im Speicher. Also in diesem Fall heißt die Adresse von ‚h‘ (von hello, world!) testmsg
. Mit testmsg
kann man jetzt immer auf den Anfang von „hello, world!“ zugreifen. testmsg + 1
ist dann das ‚e‘, testmsg + 2
das ‚l‘ usw. Was wir an dieser Stelle aber noch nicht wissen ist, wo hört "hello, world!",0xa
auf? Das machen wir in der nächsten Zeile.
testlen equ $-testmsg ; Laenge des Strings in Bytes
equ
ist auch ein Pseudo-Befehl. Mit ihm kann man einen Wert definieren. C-Programmierer kennen das von der #define Präprosessor-Anweisung. equ
(vielleicht von equal) ordnet einem Bezeichner einen Wert zu. Ein Bezeichner ist dabei einfach der Name für etwas. Z. B.: x = 3. Hier wäre x ein Bezeichner. Mit testlen equ 14
könnte man z. B. statt 14 auch überall testlen
schreiben.
Zwar hat unser hello, world! 13
sichtbare und ein unsichtbares Zeichen (den Zeilenumbruch), also insgesamt 14 Zeichen, aber würde man einen sehr langen Text ausgeben wollen, wäre es wünschenswert, nicht jedes mal die Länge per Hand nachzuzählen oder auch bei Textänderungen müsste man den Wert ständig anpassen.
NASM erlaubt es statt einen festen Wert bei einem equ Befehl, einen mathematischen Ausdruck (Expression) anzugeben. Z. B. testlen equ 13 + 1
oder man könnte auch einfach einen anderen Bezeichner angeben z. B. testlen equ testmsg
. Wie etwas weiter oben bereits erklärt enthält testmsg
die Adresse von hello, world. Da wir noch nicht genau wissen, an welcher Stelle sich genau „hello, world“ befindet, nehmen wir einfach mal an, es ist Adresse 10000 (könnte ja sein), dann hätte testlen
jetzt den Wert 1000.
Das Token $
enthält die Adresse/Position an dem es Aufgerufen wurde. Hier ein Beispiel: Wir haben eben gesagt, testmsg
hat die Adresse 1000, dann folgen 14 Bytes (hello, world …), dann kommt zwar testlen equ
etc. aber das erzeugt keine Daten in der Datei, so dass $
auf 1014 zeigt. Somit ist der Ausdruck testlen equ $ - testmsg
das Gleiche wie testlen equ 1014 - 1000
und das ist testlen equ 14
. Angenommen die Adressen sind andere, würde sich zwar die beiden Werte um einen Offset verschieben z. B. 50, aber 1064 – 1050 sind immer noch 14. So können wir die Textlänger bequem berechnen lassen.