Möchte diesen Artikel wirklich jemand lesen? Ist er nützlich oder überflüssig? Ich weiss es nicht. Ich schreibe ihn trotzdem, weil er in mir nostalgische Gefühle weckt. Irgendwann in den 80er-Jahren habe ich bei der Volkshochschule Sankt Augustin einen Programmierkurs für Assembler belegt. Dort durften wir eine Ampelschaltung programmieren. Das war mein Einstieg in die Erstellung von Software. Halt, das stimmt nicht; vorher hatte ich auf einem TI-58 ein Hi-Low-Zahlenratespiel implementiert.
Seitdem hat sich in der Programmierwelt viel getan. Es wurden Layer um Layer auf die Programmiersprachen gelegt, um die Hardwareabhängigkeit und die Lesbarkeit und Effizienz bei der Programmierung zu verbessern. Heute kommt niemand mehr auf die Idee, in Assembler zu programmieren. Selbst bei zeitkritischen Anwendungen ist ein C-Code schnell genug.
Dabei ist Assembler gar nicht die unterste Schicht der Programmierung, sondern der Maschinen-Code. Egal, welche Anwendung ihr ausführt, sei es LibreOffice oder ein einfaches "Hello-World"-Beispiel; auf eurer CPU wird schlussendlich immer Maschinen-Code ausgeführt. Die CPU versteht weder Python noch Rust; sie versteht nur Nullen und Einsen.
Vermutlich gab es in den Anfängen der Computerei ein paar Nerds in weissen Kitteln, die tatsächlich Maschinen-Code geschrieben haben.
Maschinen-Code
Ich beginne ganz unten. Damit ich euch zeigen kann, wie Maschinen-Code aussieht, verwende ich ein kompiliertes Assembler-Programm, welches wir im nächsten Kapitel erstellen werden. Es ist eine ausführbare Binärdatei, die den Namen gnulinux trägt. Das Ding gibt den Text "Hallo GNU/Linux.ch" aus. Um den Maschinen-Code anzuschauen, verwende ich einen Disassembler. Der Befehl lautet: objdump -d gnulinux
401000: 48 c7 c0 01 00 00 00 mov $0x1,%rax
401007: 48 c7 c7 01 00 00 00 mov $0x1,%rdi
40100e: 48 c7 c6 00 20 40 00 mov $0x402000,%rsi
401015: 48 c7 c2 13 00 00 00 mov $0x13,%rdx
40101c: 0f 05 syscall
40101e: 48 c7 c0 3c 00 00 00 mov $0x3c,%rax
401025: 48 c7 c7 00 00 00 00 mov $0x0,%rdi
40102c: 0f 05 syscall
Ganz links seht ihr Speicheradressen, in der Mitte steht der Maschinen-Code in hexadezimaler Darstellung und rechts findet ihr den Assembler-Code in der AT&T-Schreibweise. Was ist objdump?
Der Befehl „objdump“ in Linux ist ein Dienstprogramm, mit dem Sie Informationen über Objektdateien anzeigen können. Es wird häufig für Debugging- und Reverse-Engineering-Zwecke verwendet und bietet Einblicke in die Struktur und den Inhalt kompilierter Dateien. "objdump“ kann mit einer Vielzahl von ausführbaren Formaten umgehen, einschließlich ELF-Dateien (Executable and Linkable Format), und bietet Optionen zum Disassemblieren von Code, zur Untersuchung von Headern und mehr.
Ihr müsst das nicht installieren, weil es auf jeder Linux-Distribution vorhanden ist. Das gilt auch für die weiteren Befehle, die in diesem Artikel vorkommen.
Assembler
Assembler ist eine Low-Level-Programmiersprache, die direkt mit der Hardware kommuniziert. Assemblersprachen sind für Menschen (halbwegs) lesbare Versionen von Maschinen-Code. Wir verwenden Assembler, um Assembler-Code in Maschinen-Code umzuwandeln.
Jede Prozessorfamilie hat ihre eigene Assemblersprache mit unterschiedlichen Befehlssätzen. Die x86-Assemblersprache ist zum Beispiel die Assemblersprache für Intel-Prozessoren. Neben der Prozessorarchitektur kann sich auch das Format der ausführbaren Datei zwischen den verschiedenen Betriebssystemen unterscheiden. Daher kann es mehrere Assembler für dieselbe Architektur geben.
Es gibt zwei gängige Assembler-Syntaxen: AT&T und Intel. Die vorherrschende Syntax im Linux-Bereich ist natürlich die AT&T-Syntax, da Unix in den AT&T Bell Labs entwickelt wurde. So verwendet beispielsweise GCC, der Standardcompiler von Linux, standardmäßig die AT&T-Syntax. Die vorherrschende Syntax in der Windows-Domäne ist jedoch die Intel-Syntax. In diesem Artikel verwende ich die Intel-Syntax, weil sie einfacher zu lesen ist.
Beispiel gefällig?
AT&T-Syntax : mov $1, %rax
Intel-Syntax: mov rax, 1
Beide Befehle schieben den Wert 1 in das CPU-Register RAX.
Hier ist der x86 Assembler-Quellcode, um den Text "Hallo GNU/Linux.ch" auszugeben:
.global _start
.section .data
message: .ascii "Hallo GNU/Linux.ch\n"
.section .text
_start:
mov rax, 1
mov rdi, 1
mov rsi, offset message
mov rdx, 19
syscall
mov rax, 60
mov rdi, 0
syscall
Was passiert hier?
.global _start legt den Einstiegspunkt ins Programm fest; so ähnlich wie die main() Funktion in C-Programmen.
In .section .data können Variablen belegt werden. Im Beispiel ist es der ASCII-Text "Hallo GNU/Linux.ch" mit einem Zeilenumbruch am Ende.
.section .text kündigt den Beginn der eigentlichen Verarbeitung an. Mit _start: geht es dann los. Dann kommen ein paar mov-Befehle, mit denen Werte in CPU-Register geschrieben werden. Ich erkläre hier nicht die Bedeutung der einzelnen Register; falls euch das interessiert, könnt ihr das hier lesen. Nur so viel, im Register rdx muss die Länge des Strings stehen, wobei der Zeilenumbruch \n am Ende als ein Zeichen zählt.
Mit syscall wird der Systemaufruf sys_write() ausgelöst, der die Werte tatsächlich in die CPU-Register schreibt.
Die 60 in rax beendet das Programm (sys_exit) und die 0 in rdi ist der positive Rückgabewert für sys_exit.
Kompilieren
Bisher haben wir nur ein Stück Quellcode in Assembler geschrieben. Um daraus ein ausführbares Programm zu machen, sind zwei weitere Schritte nötig: build und link. Zum Bauen kommt dieser Befehl zum Einsatz:
as -msyntax=intel -mnaked-reg gnulinux.asm -o gnulinux.o
as ist der GNU Assembler Compiler. Die beiden Parameter dienen dazu, dem Compiler klarzumachen, dass wir die Intel-Syntax verwenden. gnulinux.asm ist der Quellcode und gnulinux.o ist der Object-Code, eine ELF-Datei (siehe oben). Um daraus eine ausführbare Datei zu machen, rufen wir den Linker ld auf:
ld -s -o gnulinux gnulinux.o
Die beiden Parameter -s und -o erkläre ich nicht. gnulinux ist das Executable und gnulinux.o ist die Input-Datei für den Linker. Danach gibt es das ausführbare Programm gnulinux, welches man mit ./gnulinux starten kann. So sieht die Ausgabe aus:
Fazit
Laut dem Tiobe-Index ist Python erneut als beliebteste Programmiersprache ausgewählt worden. Der Quellcode für das "Hello World"-Beispiel ist in Python nur eine Zeile lang: print('Hello World'). Doch darum geht es mir in diesem Artikel nicht. Ich wollte euch auf den Urahnen der Programmierung hinweisen. Es ist 'erst' vierzig Jahre her, als Assembler noch als Programmiersprache - insbesondere für zeitkritische Anwendungen - verwendet wurde.
Titelbild: https://www.osa.fu-berlin.de/informatik_lehramt/_medien/bild_assembler/assembler_930.jpg
Quellen:
https://computerhistory.org/blog/programming-the-eniac-an-example-of-why-computer-history-is-hard/
Danke, an meine Anfänge mit Maschienencose Programmierung musste ich vor kurzen auch denken. Auch an meine Anfänge neuronale Netze in einem DSP zu programiern (ca. 1990), also so ne art mini KI, und dieses dann zu trainieren.
Ich hab mal gelesen, dass das Spiel Rollercoaster Tycoon 1 und der Emulator No$GBA mehr oder weniger in Assembler geschrieben wurden. Vor allem letzterer war der Konkurrenz in Sachen Leistung und Ressourcenschonung haushoch überlegen. Moderne Sprachen wie Nim sind aber definitiv einfacher zu lesen: echo "Hallo GNU/Linux.ch"
Wir verwenden heute noch Assembler auf Mikrocontrollern. Die 8 Bit, 16 Bit oder 32 Bit Mikrocontroller kommen in Steuergeräten von vielen Fahrzeugen zum Einsatz. Wie Ralf es schon schrieb, wird das nur bei zeitkritischen Programmteilen eingefügt.
Ach, wäre ich doch nochmal jung - und hätte das lernen können. Für mich als "Nichtinformatiker" und gewöhnlicher Linux User nur "Chinesisch"...
Und mehr oder weniger zurück...
$ objdump -d -M intel gnulinux
Ein paar Links für Leute, die sich etwas mehr mit Assembler beschäfigen möchten:
Fresh-IDE - Visuelle Assembler IDE als Open Source: https://fresh.flatassembler.net/ Dokumentation, Forum und viele Beispiele gibt es hier: https://flatassembler.net/
Online Assembler & Disassembler: https://defuse.ca/online-x86-assembler.htm oder https://shell-storm.org/online/Online-Assembler-and-Disassembler/
Anleitung für den SEKA Assembler, der bekannteste für den Motorola 68000 Prozessor: https://zrk.dk/seka/
Danke für die interessanten Links! Die Online-IDEs kannte ich alle noch nicht.
Dann ergänze ich mal für Neugierige eine tolle Doku zu dem damals bahnbrechenden Spiel "Elite", das ohne Assembler – und jede Menge kluger Tricks – nicht denkbar gewesen wäre: https://youtu.be/lC4YLMLar5I
Dessen Quellcode ist hier dokumentiert, kommentiert und organisiert: https://elite.bbcelite.com/
ROFL Der Dino-Nerd (60J.) hat schon reichlich 'rumgemurkst: SC/MP Assembler 6510 (C64) 68000 / 68030+FPU (Amiga) MIPS-el (Casio e-105) ARM (diverse)
Ich bin kein Experte - nur ein bekloppter Freak Liebe Grüße aus Berlin
Mit Assembler wurden auch sehr kleine und schnelle Betriebssysteme geschrieben. Die sind vor allem für Bastler und eingebettete Systeme interessant.
Für x86 gibt es zum Beispiel KolibriOS . Für ARM, wie beim Raspi, gibt es RISC OS .
Die Links wurden verschluckt. Ich versuche es nochmal.
KolibriOS: https://kolibrios.org/de/
RISC OS: https://www.riscosopen.org
Danke. Sehr interessant.