Über Assembler sprechen Sie mit dem Rechner fast in seiner Muttersprache
Um der Wahrheit die Ehre zu geben: Es gibt es heute nur noch wenige Bereiche, in denen der Einsatz von Assembler sinnvoll ist. Für hardwareaffine Bastler bleibt es aber ein interessantes Thema, bei dem es viel zu lernen gibt. Assembler-Programme arbeiten direkt auf der Hardware des Rechners. Dadurch erreichen sie fast die maximal mögliche Geschwindigkeit beim Ausführen. Gerade bei einem PC wie dem RasPi mit seinen eingeschränkten Ressourcen ist es daher unter bestimmten Umständen besser, in Assembler zu schreiben. Um aber entsprechende Programme zu erstellen, muss man sich ein Stück weit mit der Architektur von CPU und Peripherie beschäftigen.
Maschinencode
Vorab lohnt es sich, kurz einige Begriffe zu klären. Die CPU selbst versteht nur Maschinencode. Das sind tatsächlich nur Nullen und Einsen oder genauer gesagt Spannungspegel, die Nullen und Einsen repräsentieren. Für jedes Kommando im Maschinencode existiert eine für Menschen lesbare und leicht zu merkende Abkürzung. Diese heißen Mnemonics und dienen als Assembler-Befehle. Assembler-Code ist spezifisch für eine CPU-Architektur. Das bedeutet, dass der Code für einen Raspberry Pi (ARM) nicht auf einem PC (x86) läuft.
Grundsätzlich gibt es zwei Ansätze, um auf dem RasPi in Assembler zu programmieren: Zum einen können Sie ein Image erstellen, in das Sie den Code verpacken. Dann booten Sie den Mini-PC davon und führen das Programm aus. Anders gesagt: Sie degradieren den RasPi zu einem Mikrocontroller. Bei dieser Methode arbeitet der Rechner komplett ohne Betriebssystem. Sie haben zwar vollen Zugriff auf alles, erhalten aber nicht einmal eine Shell.
Der zweite Weg besteht darin, das Assembler-Programm auf dem RasPi selbst zu starten. Dann haben Sie den Luxus eines Betriebssystems mit allem, was dazugehört, sind aber beim direkten Zugriff auf die Hardware eingeschränkt. Für das Beispiel in diesem Beitrag kommt die zweite Methode zum Einsatz.
Setup
Als Basis für die Experimente dient ein RasPi 3 mit dem aktuellen Raspberry Pi OS Lite. Um die SD-Karte vorzubereiten, kommt der Raspberry Pi Imager [1] zum Einsatz. Nach dem Booten von der Karte geht es direkt los, denn alle nötigen Tools zum Programmieren in Assembler liegen bereits im Image. Ein zusätzlicher Handgriff sorgt aber trotzdem für mehr Komfort und Flexibilität (siehe Kasten “SSH aktivieren”).
SSH aktivieren
Um einen zusätzlichen Monitor mit Tastatur einzusparen, empfiehlt es sich, über SSH auf dem Raspberry Pi zu arbeiten. Damit der passende Dienst direkt beim ersten Start läuft, braucht es nur einen kleinen Handgriff: Legen Sie auf der SD-Karte im Verzeichnis /boot eine leere Datei mit dem Namen ssh an, dann startet der SSH-Daemon beim Booten automatisch.
Bei Bedarf leiten Sie mit der Option -X die Ausgabe des X-Servers via SSH vom RasPi auf den Desktop-PC um. Am einfachsten funktioniert das, wenn Sie auf dem Schreibtischrechner ebenfalls Linux verwenden. Unterstützt Ihr Router eine lokale Namensauflösung, bauen Sie mit dem Kommando ssh -X pi@raspberrypi@local die Verbindung auf. Alle grafischen Ausgaben von Programmen landen dann auf dem Desktop-Rechner. Klappt das lokale DNS nicht, verwenden Sie die IP-Adresse des RasPi. Die suchen Sie aus der Liste der verbundenen Geräte im Router heraus.
Kein “Hello World”
Die Arbeit mit einer neue Programmiersprache beginnt meist mit dem bekannten Hello-World-Programm. Allerdings erfordert es schon einiges an Code sowie Verständnis für die Konzepte, um mit Assembler eine einfache Ausgabe auf der Konsole zu erzeugen. Daher gibt unser erstes kleines Assembler-Programm nur den Return Code auf der Konsole aus.
Dabei bedeutet der Return Code 0, dass das zuvor ausgeführte Kommando fehlerfrei lief, ein Wert größer null deutet auf einen Fehler hin. Listing 1 zeigt das Beispiel. Es trägt den Namen 42, weil es den Wert 42 als Return Code liefert. Sicher haben Sie schon gemerkt, dass die Headline dieses Artikels 42 in BCD-Kodierung ist.
Listing 1
Programm 42.s
.global main /* Einstiegspunkt für das Programm */ main: mov r0, #42 /* Wert 42 in Register r0 schieben */ bx lr /* Rücksprung zum aufrufenden Programm */
Assembler besteht aus relativ einfachen Kommandos, die nur Bytes hin und her schieben, diese manipulieren oder auf ein Status-Bit reagieren. Daher ist es extrem wichtig, den Code gut zu dokumentieren und, wo möglich, sprechende Bezeichner zu verwenden.
Labels kommen in Programmiersprachen zum Einsatz, um Punkte im Quellcode zu markieren, die als Sprungziele dienen. Dabei tauscht der Compiler das Label während des Übersetzens gegen eine reale Adresse im Speicher aus. Das erklärt den enormen Vorteil, den die Verwendung von Labels mit sich bringt: Sie brauchen nicht mühselig zu errechnen, an welcher Stelle im Speicher sich ein bestimmtes Kommando befindet. Bei jedem zusätzlich eingefügten Kommando würden sich zudem alle Adressen unterhalb davon verschieben.
Wie in vielen anderen Programmiersprachen legen Sie auch in Assembler den Startpunkt für ein Programm fest. In Java und C heißt die entsprechende Funktion main(), in Assembler definieren Sie ein globales Label mit dem Namen main. Das Label muss vor der ersten Code-Zeile stehen, die Sie ausführen wollen. Das Assembler-Programm aus Listing 1 verdeutlicht das.






