- Nativer Code
-
Die Maschinensprache (auch Maschinencode oder Nativer Code genannt) bezeichnet ein System von Instruktionen, die der jeweilige Prozessor eines datenverarbeitenden Systems direkt und ohne Kompilierung ausführen kann.
Im Gegensatz zur Assemblersprache oder Hochsprachen handelt es sich um einen für den Menschen kaum verständlichen Binärcode, der nur von Experten für den jeweiligen Code gelesen werden kann und meist mit speziellen Programmen, so genannten Maschinensprachemonitoren (abgekürzt auch Monitor genannt), bearbeitet wird. Aber auch dabei wird der Code selbst in einfachsten Debuggern zwecks besserer Lesbarkeit fast ausschließlich in hexadezimaler Darstellung präsentiert.
Der Maschinencode wird in der Regel von einem Assembler oder Compiler erzeugt. Direkt in Maschinensprache muss nur programmiert werden, wenn kein Assembler für den Zielprozessor zur Verfügung steht.
Wird von der Programmierung in Maschinensprache gesprochen, wird heute üblicherweise die Maschinenprogrammierung in Assemblersprache unter Verwendung eines Assemblers gemeint, der das als Textdatei vorliegende Assemblerprogramm in binäre Maschinenbefehle übersetzt.
Für die Ausführung/Übersetzung von Maschinencode auf systemfremde (also ungeeignete) Prozessoren existieren Emulatoren.
Inhaltsverzeichnis
Unterschiede zur Assemblersprache
Die Maschinensprache besteht aus einer Folge von Bits/Bytes. Da sie in dieser Form für den Menschen praktisch unlesbar sind, wird jede zulässige Bitfolge mit einem oder mehreren Namen (Mnemonics) – und eventuell hexadezimalen Zahlen dargestellt.
Darüber hinaus sind weitere vereinfachende Werkzeuge (Tools) erstellt worden.
Das wichtigste dieser Tools - die Assemblersprache - ist dennoch keine eigenständige Sprache oder Hochsprache. Ihre Aufgabe besteht lediglich darin, dem Programmierer einige "Unbequemlichkeiten" der reinen Maschinensprache abzunehmen. Eine wirkliche Umformung von Befehlen in einzelne Befehlssequenzen (wie bei der Compilierung aus Hochsprachen) findet dabei nicht statt.
Ein Assembler übersetzt nach der Programmerstellung die Anweisungen meist nahezu eins zu eins in Mnemonics bzw. Maschinensprache.
Zahlenformate: Einer der Vorteile eines Assemblers ist es, dass er dem Programmierer erlaubt numerische Werte in jedwedem Zahlenformat (dezimal, Hexadezimal, oktal, binär) zu codieren.
Datendeklaration: Ein Assembler bietet dem Programmierer manchmal gewisse Freiheiten/Unsauberkeiten in der Deklaration von Daten (Integer, LongInteger, initialisiert und uninitialisiert etc.) an, die er meist selbst in der Verwandlung zu einem Maschinenprogramm glattbügelt.
Adressierungsarten: Eines der lästigen Probleme reiner Maschinensprache ist die Tatsache, dass einfache Sprunganweisungen (Jumps) absolut angegeben werden müssen. Selbst kleine Erweiterungen des Programms erfordern deshalb oft eine mühselige Anpassung absoluter Sprungadressen. Die Verwendung eines Assemblers ermöglicht es, alle Sprunganweisungen relativ (z.B. über Labels) als symbolische Adressen zu definieren. Die Übersetzung in absolute Adressen übernimmt dann der Compilierungsprozess des Assemblers. Außerdem ist eine Adressierung über Labels (wie z.B. Call Additionsroutine) später viel eher nachvollziehbar als ein relativ nichtssagender Befehl wie z.B. JMP 0D80:0410.
Kommentierung: Eine Assembler ermöglicht es, den einzelnen Programmanweisungen erklärenden Kommentar hinzuzufügen. Dies ist besonders in Programmen die in Teamarbeit entstehen eine unabdingbare Voraussetzung der Softwareerstellung.
Optimierung: Viele Assemblerprogramme garantieren dem Anwender eine primär zeitliche Optimierung des übersetzten Maschinenprogramms in Abhängigkeit vom jeweilig verwendeten Prozessor. So versprach Borland bei seinem Turbo-Assembler von 1988 durch die Verwendung der Anweisungen .186, .286 oder .386 eine optimierte Anpassung an den jeweiligen Prozessortyp. Auch die Verwendung von Coprozessoren kann dabei meist angemeldet werden. [1]
Tendenzen zu Hochsprachen: Mit Assemblerbefehlen wie Macro werden auch eher objektorientierte Ansätze zur Zusammenfassung von Einzelbefehlen in Assembler im Ansatz rudimentär angelegt.
Perspektiven: Eine reine maschinensprachliche Programmierung bringt - speziell in der heutigen Zeit - keine Vorteile. Selbst die Programmierung in der etwas höheren Assemblersprache scheint angesichts relativ hardwarenaher Sprachen wie C und C++ ziemlich sinnlos zu sein. Selbst Borland und Microsoft haben den Vertrieb eigener Stand-Alone-Assembler seit 10 Jahren mangels Nachfrage eingestellt. [2]
Überblick über die typische Funktionalität einer Maschinensprache
Jede Maschinensprache kann nur den Befehlsvorrat des jeweiligen Prozessors verwirklichen.
Befehlsvorrat
Zuordnung: Fast alle Befehle (außer z.B. RET oder NOP) erfordern genau definierte Register oder Speicherpositionen als Datenquellen. Der Prozessor gibt seine Ergebnisse und relevante Zusatzinformationen über genau definierte Ziele und/oder das Setzen von Flags zurück. Dies ermöglicht es dem weiteren Programmablauf diese Informationen auszuwerten und darauf zu reagieren. Die Größe von Quell- und Zieloperanden ist dabei befehlsabhängig unterschiedlich (Byte, Word, DoubleWord, QuadWord).
Beispiel: Ein Additionsbefehl wie ADC (add with carry) signalisiert dem weiteren Programmablauf ein Überschreiten des gültigen Wertebereichs über das Setzen des Carry- und Overflow-Flags hinaus.
Unterschiede: Der Befehlsvorrat einzelner Prozessoren ist unterschiedlich. Nicht alle Befehle sind auf jedem Prozessortyp und in jeder Prozessor-Generation verfügbar.
Beispiel: Ein einfacher Grundbefehl wie SHL/SHR, der einen Registerwert um eine bestimmte Anzahl von Stellen nach links oder rechts verschiebt ist schon im 8086 vorhanden. Die mächtigere Variante SHLD/SHRD welche zusätzlich die entstehenden Leerstellen aus einem anderen Integerwert auffüllt, ist erst ab dem 80386 implementiert.
Mächtigkeit: Der Befehlsvorrat eines Prozessors stellt dabei Befehle unterschiedlich mächtiger Funktionalität bereit. Neben einfachen, einstufigen Grundoperationen stehen Befehle zur Verfügung welche streng genommen eigentlich zwei Operationen in einem Befehl vereinigen.
Beispiele: Der Befehl CMP (compare) ermöglicht den Vergleich zweier Werte auf <,>, =. Der Befehl XHHG (exchange) vertauscht die Positionen zweier Operanden. Der Befehl CMPXCHG (compare and exchange) kombiniert nun praktisch diese beiden Befehle und ermöglicht einen bedingungsabhängigen Datentausausch in einem Befehl. Während der Befehl BT (bit test) nur den Zustand eines einzelnen Bits in einem Integerwert prüft, ermöglichen es die Befehle BTC, BTR, und BTS darüber hinaus das geprüfte Bit abhängig vom Ergebnis der Prüfung zu setzen (BTS), zu löschen (BTR), oder zu invertieren (BTC).
Performance: Jeder Befehl wird in einer in Datenblättern angegebenen Anzahl von Taktzyklen des Prozessors abgearbeitet. Das ermöglicht dem Programmierer bei extrem zeitkritischen Anwendungen beispielsweise einige mehrtaktige Befehle durch wenigere, in der Summe aber geringertaktige Befehlsketten (aber auch umgekehrt einen vieltaktigen Befehl durch mehrere in der Summe zyklisch kürzere Befehle) zu ersetzen.
Kategorisierung der Befehle
Grundlegende Maschinen-Befehle lassen sich in folgende Kategorien unterteilen:
- Arithmetische Operationen: Führen Berechnungen durch (ADD, ADC, SUB, SBB, DIV, MUL, INC, DEC)
- Logische Operationen: Verknüpfen Bitfelder logisch miteinander (AND, OR, XOR, NOT)
- Bit-orientierte Operationen: Mit ihnen kann man einzelne Bits in einem Bitfeld genau ansprechen, auslesen (BSF, BSR), verschieben (SHL, SHR, RCL, RCR, ROL, ROR) bzw. manipulieren (BT, BTC, BTR)
- Speicheroperationen: Übertragen Daten zwischen Prozessorregistern (MOV, MOVSX, MOVZX, XCHG), innerhalb eines Registers (BSWAP), sowie Registern und Speicher
- Vergleichsoperationen: Vergleich von Werten mittels <, >, sowie = (CMP, TEST)
- Kombinierte Befehle aus Vergleichsoperationen, arithmetischen Operationen, und Datenaustausch (XADD, CPPXCHG)
- Steueroperationen: Verzweigungen, die den Ablauf des Programms beeinflussen
- Datenkonvertierung: Diese Befehle erweitern den Wertebereich einer Ganzzahl (Integer) in ein größeres Format. Z. B. ein Byte in ein Word (CBW), ein Word in ein Doubleword (CWD), oder ein Doubleword in ein Quadword (CDQ). Befehle wie CVTWB (Convert Word to Byte) oder CVTLB (Convert Long to Byte) wandeln in ein kleineres Format um.
In vielen modernen Prozessoren sind die Befehle der Maschinensprache, zumindest die komplexeren unter ihnen, intern durch Mikroprogramme realisiert. Das ist insbesondere bei der sogenannten CISC-Architektur der Fall.
Programmerstellung
Intern ist jeder Befehl der Maschinensprache durch ein oder mehrere Zahlenwerte kodiert. Diese Zahlenwerte bestehen aus dem Opcode, der die Art des Befehls festlegt, eventuell gefolgt von einem oder mehreren Bytes an Daten zu diesem Befehl. Eine sinnvolle Folge von solchen Zahlencodes im Hauptspeicher bzw. als Datei gespeichert bildet demnach ein Programm. Es gibt nun verschiedene Arten, solche Programme zu erstellen:
- Direkte Eingabe der Binärcodes über eine Reihe von Schaltern (äußerst kryptisch und unpraktisch, seit den 1970er Jahren völlig außer Gebrauch gekommen)
- Über einen Hex-Editor den Zahlen-Code in Opcodes zu schreiben. (immer noch sehr kryptisch und unpraktisch)
- Mit einem Assembler: Assemblersprachen formulieren die Prozessorbefehle des Maschinencodes als Mnemonics in einer einfachen, relativ leicht lesbaren Syntax. Dieser Quelltext wird danach vom Assembler in den Maschinencode konvertiert.
- Ein Programm wird in einer relativ abstrakten Hochsprache geschrieben, danach von einem Compiler in Maschinencode übersetzt (kompiliert).
- Alternativ können Programme in einer Hochsprache auch – entweder nach Kompilierung in einen Zwischencode oder direkt – durch einen Interpreter abgearbeitet werden.
Ein Beispiel hierfür ist die Programmiersprache Java, deren Zwischencode (auch Bytecode genannt) von einem Interpreter ausgeführt wird. Dies geschieht für den Benutzer transparent, wenn z. B. ein Applet im Internet Browser ausgeführt wird. Neben Java werden auch sämtliche .NET Sprachen, wie beispielsweise C#, in einen Zwischencode (engl. Intermediate Language) übersetzt, welcher anschließend zur Laufzeit innerhalb der CLR von einem JIT-Compiler in die entsprechende Maschinensprache übersetzt wird.
Literatur
- Reiner Backer: Assembler – Maschinennahes Programmieren von Anfang an; rororo Taschenbücher Nr. 61224; (2003); ISBN 3-499-61224-0
Einzelnachweise
Wikimedia Foundation.