Assembler
« | 14 Dec 2019 | »Man könnte es als pubertäre Selbstüberschätzung werten, als ich schon in frühen Jugendjahren meinte:
Ach, Programmieren kann ich so gut, ich muss jetzt die schwerste Art Code zu schreiben lernen, nämlich Assembler.
Hmm … aus heutiger Sicht war das recht überheblich. Ich weiß nun, dass ich heute in Summe immer noch recht wenig (viel zu wenig) verstanden habe … und vor 20 Jahren war das noch viiieeel schlimmer.
Und trotzdem, bis heute profitiere ich aus den Experimenten und dem Wissen von damals …
In meiner kleinen Welt stand am Anfang QBASIC, gefolgt von Pascal. Und in beiden Fällen waren die Grenzen schnell erreicht. In QBASIC fehlte es vor allem an “ordentlichen” Dateisystem-Routinen und in Pascal an “schneller” Grafik.
Durch QuickBasic 4.5 bekam ich etwas von Interrupts
mit, die über die Bibliothek QB.BI
möglich gemacht wurden.
Und in Turbo Pascal 6 fand ich immer mehr Beispiele, wie diese Interrupts
benutzt wurden um “geheime” Systemfunktionen auszulösen.
So ganz nebenbei ergab sich daraus das Wissen, dass der Prozessor mit
ganz besonderen “Variablen” arbeitet, die als Register bekannt sind.
Eigentlich kann der Prozessor überhaupt nur mit Werten in seinen Registern
rechnen und Daten aus Variablen im Speicher, die wir aus Programmiersprachen
kennen, müssen stets in Register geladen oder aus diesen gespeichert werden.
… und das macht man in: Assembler.
Maschinensprache
Assembler ist eigentlich KEINE Programmiersprache wie C oder BASIC. Jeder Prozessortyp versteht eine bestimmte Menge an Operationscodes, die durch Bytesequenzen kodiert sind. Und da wir Menschen uns recht schwer tun, uns hunderte Bytecodes mit ihren Verknüpfungen zueinander zu merken, wurden menschen-lesbare Codes eingeführt (Mnemonics genannt), mit denen wir Maschinenbefehle als Textbefehle darstellen können.
… und das heißt dann Assembler, sieht aber für jede Plattform anders aus.
Eigentlich wurden Programmiersprache (sogenannte “Hochsprachen” oder
“high-level languages”) eingeführt, damit wir bequem mit Variablen und
Funkionen arbeiten können und uns nicht um maschinen-nahe Details kümmern
müssen.
Fakt ist aber, dass auch der beste Compiler
der besten Hochsprache immer von einer genialen Programmieridee ausgestochen
werden kann.
Das heißt also: Theoretisch kann man in Assembler “das perfekte Programm”
schreiben, in anderen Sprachen sind sie nur “annähernd perfekt” oder “gut”.
Genau das war wohl auch mein Ehrgeiz, hier in die Tiefe zu gehen.
Und tatsächlich war es zu Zeiten von MS-DOS möglich und auch teils notwendig, Programmteile mit Assembler-Tricks zu optimieren. Denn die damaligen Compiler waren gezwungen mit wenig RAM und CPU-Kraft auszukommen und konnten keine Codeanalysen durchführen, die sie heute problemlos schaffen.
Und so wurde ein Funktionsaufruf einer Hochsprache oft sehr linear in
Maschinenbefehle übersetzt.
Genau hier konnte man die Zugriffe auf Variablen im RAM reduzieren und
mehr aus den Registern herausholen.
Das Z80 Problem
Nun, hätte mir damals schon jemand gesagt, dass mein PC einen X86 Prozessor beinhaltet und ich somit nur mit einem X86-Assembler darauf programmieren kann, wäre mir das folgende peinliche Missgeschick wohl erspart geblieben.
Selbstbewusst lief ich nämlich in den Bücherladen und wollte ein Assembler-Buch kaufen. Die dortige Angestellte fischte sogleich eines aus ihrer Liste, bestellte es und eine Woche später hielt ich es in meinen Händen.
Die ersten Seiten zeigten bereits, dass das Buch wohl etwas “alt” war …
aber egal, schließlich hatte ich ja auch 15 Jahre alte Pascal Bücher gelesen
und die Befehle dort funktionierten alle noch.
Doch die Befehle im Buch waren mir alle fremd und passten so überhaupt nicht
zu jenen, die ich in Pascal schon kennen gelernt hatte.
Tja, das Buch behandelte den Z80
Prozessor, der so gut wie gar nichts mit den PC-Prozessoren gemein hatte.
Hmm, dumm gelaufen … also blieb meine Quelle für Assemblerwissen weiter
nur die eine oder andere PC-Zeitschrift, wo mal ein bisschen ASM-Quellcode
abgedruckt war.
Dennoch las ich das Buch und versuchte mich in die dort beschriebene Welt hineinzudenken. Und das war gold-richtig.
Gameboy-Emulationen und Pokemon
Ich wusste es damals noch nicht, doch einen Z80 Prozessor hatte ich schon einmal besessen. Er war in meinem Nintendo Gameboy verbaut, den ich Dummkopf kurz vor meinem Engagement für Assembler verkauft hatte um (heute unvorstellbar) eine legale Visual Basic Edition zu kaufen.
Und so lag das Buch und das Z80 Wissen nutzlos bis zum Jahr 2000 herum,
als mir ein Schulfreund einen Download aus dem Internet aufschwatzte.
Es war der “gute alte” Gameboy-Emulator NO$GMB
,
der auch auf meiner alten Gebraucht-Hardware flüssig lief.
NO$GMB
war nicht nur ein Emulator, sondern enthielt auch einen Assembler-
Debugger, mit dem die Z80-Opcodes als Mnemonics dargestellt wurden …
also genau die Befehle, die mein Z80 Buch beschrieb.
An dieser Stelle hätte es eigentlich passieren können, dass ich mich zum Z80-Assembler-Spezialist hätte entwickeln können … doch leider Fehlanzeige. Denn mein lieber Freund packte auch das damals beliebteste Gameboy Spiel dem Emulator bei, und das war: Pokemon - Blaue Edition.
Damit dauerte es nicht lange, dass ich das Buch wieder bei Seite legte und eine bis heute andauernde Zuneigung zu den kleinen virtuellen Kreaturen aufbaute.
Perfektion
Eines habe ich aber sehr schnell zu respektieren gelernt: Die Macher von
Gameboy-Spielen, die aus Platzgründen häufig direkt in Assembler arbeiten
mussten, waren absolute Genies der Programmierung.
Sie schafften es, mit wenigen Maschinenbefehlen das hinzubekommen, wofür
Compiler hunderte Bytes hätten generieren müssen.
Das beginnt bei Limitierungen, wie einer fehlenden Multiplikationseinheit auf der CPU und endet bei der Nutzung von einer Hand voll Bytes, die reichten um den ganzen Bildschirm mit Grafiken zu füllen.
Ja ich bewundere jene Vollblutentwickler, die sich in Maschinencodes hineindenken und “perfekte” Lösungen für ein Problem schaffen. Und es ist fast schade, dass die heutigen Leistungsdaten unserer Computer und der gebotene Komfort unserer Compiler uns diese alte ehrwürdige Kunst langsam vergessen lassen.
X86 Assembler
Nach dem Z80-Buch-Kauf folgte der Kauf meiner Turbo-Assembler 4.0 CD (die im Jahr 2000 auch schon etwas veraltet war). Damit hatte ich die folgenden Jahre immer wieder Spaß, brachte aber leider keine wirklich konkreten Programme hervor. Ein DOS-basierter Text-Editor war so ziemlich das höchste der Gefühle.
Sehr interessant waren allerdings viele auf der CD gesammelten Codebeispiele,
für die unterschiedlichsten Probleme aus der “guten alten DOS-Welt”.
Doch leider wurde auch dieses Wissen mit dem Jahr 2000 mehr und mehr
obsolet. Und sowohl unter Windows als auch unter Linux nahmen C und C++ mit
ihren immer besser werdenden Compilern den Assembler-Gurus den Wind aus
den Segeln.
Der 32-bit X86 Assembler begleitet mich jedoch bis heute weiter. Wenn es darum geht, den Callstack einer Exception-Behandlung nachzuvollziehen oder zu sehen, ob ein Template-Konstrukt besser ist als ein Makro, dann schalte ich für C++ den Assembler-Source-Generator ein, um zu sehen, wie der C++ Compiler meine Anweisungen übersetzt.
Da kommen oft interessante Ergebnisse zum Vorschein, wie dass Template-Code mal besser aber leider auch mal schlechter kompiliert wird, als mehrere makro-isierte statische Funktionen.
Am interessantesten sind aber Funktionen, wo der Compiler intelligente
mathematische Zusammenhänge erkennt, die mir als Programmierer verborgen
blieben. z.B.: wenn ich einen switch(...)
Block nutze um unterschiedlich
Return-Codes zurückzugeben und der Compiler das in 3 oder 4 Rechenzeilen
übersetzt, wo der “geswitchte” Code ohne Vergleiche in die Returncodes
umgerechnet wird. (Und damit meine ich nicht nur simple Additionen und
Multiplikationen).
Fazit
Assembler ist und bleibt für die höchste Disziplin in der Programmierung.
Wirtschaftlich relevant ist sie heute leider nicht mehr. Die einzige
Branche, die sie noch aktiv nutzt, sind die Compilerbauer selbst,
gefolgt von einigen wenigen Mikrocontroller-Puristen und gelegentlich mal
bei einem Treiber.
Hier sehne ich mich doch etwas nach der Zeit, wo alles noch “schlechter” war, weil man dann mit Assemblerwissen ein kleiner Wunderheiler sein konnte.