Maschinensprache


Maschinensprache

Die Maschinensprache (auch Maschinencode oder nativer Code genannt) bezeichnet ein System von Instruktionen, die der jeweilige Prozessor eines datenverarbeitenden Systems direkt ausführen kann.

Der Maschinencode wird in der Regel von einem Assembler (aus Assemblercode) oder Compiler (aus dem Quelltext einer höheren Programmiersprache) generiert. Direkt in Maschinensprache wird praktisch nicht (mehr) programmiert. Ausnahmen könnten z. B. sog. „Patches“ sein, mit denen in Programmen, zu denen kein Quellcode mehr vorliegt oder für die kein Übersetzer mehr existiert, minimale Änderungen vorgenommen werden können.

Im Gegensatz zum Programmcode der Assemblersprache oder von Hochsprachen ist der binäre Maschinencode für den Menschen kaum lesbar – außer für Experten, die ihn zeichenweise interpretieren können und dabei meist spezielle Programme benutzen, so genannte Maschinensprachemonitoren oder beim Testen auch Debugger. Fast ausschließlich wird der Maschinencode dabei hexadezimal dargestellt – zum Beispiel in sog. Dumps, die den Inhalt des Hauptspeichers zeigen, wenn ein Programm „abgestürzt“ ist und der Programmierer mit diesem Hilfsmittel die Fehlerursache analysieren will.

Wird von der Programmierung in Maschinensprache gesprochen, ist heute üblicherweise die Programmierung in Assemblersprache unter Verwendung eines Assemblers gemeint, mit dem der Assemblercode in Maschinencode übersetzt wird.

Der Prozessor führt nicht nur Anwendersoftware über Maschinencode aus, sondern (ggf. in mehreren Softwareebenen) alle Programme, auch Systemprogramme. Selbst das Betriebssystem wird vom Prozessor – wie jeder andere Code auch – als Maschinencode abgearbeitet.

Für die Ausführung von Maschinencode auf systemfremden Prozessoren oder die Übersetzung in deren Sprache existieren Emulatoren.

Inhaltsverzeichnis

Unterschiede zur Assemblersprache

Das Programm im Maschinencode besteht aus einer lückenlosen Folge von Bits und Bytes, die sowohl Befehle als auch speicherinterne Daten repräsentieren. Da dieser Code für den Menschen praktisch kaum interpretierbar ist, werden in der Assemblersprache die Befehle durch Mnemonics (z. B. 'C' für 'compare') dargestellt, Quell- und Zielfelder sowie andere Angaben in den Befehlen können mit symbolischen Namen ('ZEILE') notiert werden, ggf. ergänzt um numerische Zahlenwerte (z. B. für eine individuelle Längenangabe, Register etc.).

Anweisungen: Ein Assembler übersetzt nach der Programmerstellung diese Anweisungen in die Maschinensprache. Damit wird der Programmierer von den „Unbequemlichkeiten“ der reinen Maschinensprache entlastet. Die Umformung erfolgt jedoch in der Regel (außer z. B. bei Verwendung von Makros) im Modus 1:1, eine 1:n-Umformung von Befehlen wie bei der Kompilierung aus Hochsprachen findet i. d. R. nicht statt.

Zahlenformate: Einer der Vorteile eines Assemblers ist es, dass er dem Programmierer erlaubt, numerische Werte in jedem Zahlenformat (dezimal, Hexadezimal, oktal, binär) zu codieren oder die Felder auch symbolisch zu benennen.

Datendeklaration: Ein Assembler bietet dem Programmierer manchmal gewisse Freiheiten/Unsauberkeiten in der Deklaration von Daten (Integer, LongInteger, initialisiert und uninitialisiert usw.) an, die er zum Teil bei der Umwandlung in ein Maschinenprogramm korrigiert.

Adressierungsarten: Eines der lästigen Probleme reiner Maschinensprache ist die Tatsache, dass für alle Befehle Speicheradressen angegeben werden müssen. Selbst bei kleinen Änderungen des Programms ist dann oft eine mühselige Anpassung dieser Adressen erforderlich. Die Verwendung eines Assemblers ermöglicht es, zum Beispiel über Labels symbolische Adressen zu definieren, die dann der Kompilierungsprozess des Assemblers in physikalische Adressen übersetzt. Außerdem ist eine Adressierung über Labels (wie zum Beispiel Call Additionsroutine) später viel eher nachvollziehbar als ein relativ nichtssagender Befehl wie zum Beispiel JMP 0D80:0410.

Kommentierung: Ein Assembler ermöglicht es, den einzelnen Programmanweisungen erklärende Kommentare 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 zehn Jahren mangels Nachfrage eingestellt.[2] Manche Compiler für Hochsprachen unterstützen das direkte Einbinden von Assemblercode in Programme (sogenannter Inline-Assembler). Dadurch können Programmteile, die beispielsweise besonders zeitkritisch sind, in Assembler geschrieben werden, ohne die generellen Vorteile der Hochsprache zu verlieren. Allerdings verliert man dabei die Portabilität, die eigentlich für höhere Programmiersprachen charakteristisch ist.

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 zum Beispiel 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.

Beispiel

Das Beispiel gilt für IBM-Großrechner, z. B. der Serie OS/390: Der Maschinencode wird beim Assemblieren oder beim Compilieren für jedes Programm erzeugt. Vor Ausführung des Programms liegen diese 'ausführbaren Programme' in einer Programmbibliothek, zur Ausführung werden sie in den Hauptspeicher geladen. Der Maschinencode enthält Befehle und Daten. Beim Laden des Programms sind die Datenbereiche leer bzw. nur bei definierten Konstanten mit Inhalt belegt.

Die Daten werden entsprechend dem festgelegten Speicherformat gespeichert; der Wert „12“ kann dabei z. B. folgendes Aussehen haben (Darstellung hexadezimal in minimaler Länge):

F1F2 Text oder ungepackte Zahl
012C gepackt positiv, Speicherung je Zahl ein Halbbyte, am Ende ein Vorzeichen-Halbbyte.
012D gepackt negativ (dto)
0C binär positiv, entspricht B'00001100'

Bei längeren Datenfeldern existieren ggf. führende Nullen zusätzlich oder bei Text nachfolgende Leerstellen. Für jedes vorgesehene Datenfeld ist eine 'Adresse' festgelegt, an der es beginnt und wo es entsprechend seiner Länge und seinem Format gespeichert ist bzw. werden soll.

Die Befehle bestehen aus dem Befehlscode und – je nach Befehl – Parametern unterschiedlicher Struktur. Alle Teile sind hexadezimal (wie dargestellt) gespeichert; die Interpunktion dient lediglich der übersichtlicheren Darstellung. Befehlsbeispiele:

C5.1C.92A4.8C2B:

C5 = Befehlscode für CLC = Compare logical charakter; Zeichenvergleich
1C = Länge minus 1 der zu vergleichenden Felder (bei 00 wird 1 Byte verglichen usw., hier also 29 Bytes)
92A4 = Adresse erster Operand: 9 = Basisregister, 2A4 = Distanz zum Register
8C2B = Adresse zweiter Operand: 8 = Basisregister, C2B = Distanz zum Register

47.80.B654:

47 = Befehlscode für BC = Branch on Condition: Sprungbefehl wenn Bedingung (aus Vorbefehl) erfüllt ist
8 = Bedingung; hier: wenn 'gleich', mnemotechnischer Assemblercode BE (branch on equal)
0 = optional Register, das einen Wert enthält, der zur Sprungadresse hinzuaddiert wird, nicht bei '0'
B = Zieladresse (Basisadresse)
654 = Zieladresse (Distanz); bei Inhalt von B = 6C4410 erfolgt die Verzweigung nach Adresse 6C4A64

<usw>

In Assembler könnte diese Codierung z.B. wie folgt aussehen:

CLC FELDA(29),FELDB
BE XXX

Von einer Hochsprache generiert könnte der Quellcode dagegen lauten:

IF Feld_A = Feld_B then GOTO XXX.

Bei „Bedingung erfüllt“ wird nach XXX (= reale Adresse 6C4A64) verzweigt, andernfalls wird im Maschinencode mit <usw> fortgefahren. Häufig generieren Hochsprachen zusätzliche Befehle, etwa um Feldlängen oder Datenformate zu egalisieren, Register zu laden, Adressen in Arrays zu berechnen etc.

Man erkennt, dass die Befehle unterschiedliche Längen aufweisen. Das Steuerwerk des Rechners erkennt die Länge an den ersten beiden Bits des Befehlscodes und schaltet das Befehlszählregister dem entsprechend weiter. An genau dieser Stelle wird das Programm fortgesetzt – falls kein Sprungbefehl auszuführen ist.

Speicheradressen werden im Maschinencode immer aus dem Inhalt eines (oder zweier) Register(s) plus optional der angegebenen 'Distanz' dargestellt. Zur Ausführung wird bei Programmstart ein bestimmtes Register vom Betriebssystem mit der Adresse geladen, an die das Programm in den Speicher geladen wurde. Von diesem Wert ausgehend, werden im Programmcode (bei ASS programmiert, bei Hochsprachen generiert) die Basisregister geladen, wodurch die mit relativen Adressen versehenen Befehle die tatsächlichen Speicherstellen ansprechen.

Überblick über die typische Funktionalität einer Maschinensprache

Befehlsvorrat

Hinweis: Die genannten Befehlskürzel sind nur Beispiele; Befehle werden prozessorabhängig unterschiedlich benannt.

Adressierung und Ergebnisanzeige: Fast alle Befehle adressieren die betroffenen Speicherpositionen (häufig Quelle/Ziel, zu vergleichend/Vergleichswert etc.) über definierte Register. Ebenso gibt der Prozessor seine Ergebnisse und relevante Zusatzinformationen über festgelegte Register und/oder über Flags zurück. Dies ermöglicht es, im weiteren Programmablauf diese Informationen auszuwerten und darauf zu reagieren. Die Länge der Befehle ist befehlsabhängig unterschiedlich, Größe von Quell- und Zieloperanden ebenfalls, zum Beispiel (Bit, Byte, HalfWord, Word, DoubleWord, QuadWord, variabel lang).

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, die streng genommen eigentlich zwei Operationen in einem Befehl vereinigen.

Beispiele: Der Befehl CMP (compare) ermöglicht den Vergleich zweier Werte auf <,>, =. Der Befehl XCHG (exchange) vertauscht die Positionen zweier Operanden. Der Befehl CMPXCHG (compare and exchange) kombiniert nun praktisch diese beiden Befehle und ermöglicht einen bedingungsabhängigen Datenaustausch 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).

Generell unterscheidet man zwischen CPUs mit RISC- (Reduced instruction set computer) oder CISC- (Complex instruction set computer) Befehlssatz. Erstere haben einen bedeutend weniger mächtigen Befehlssatz, können jeden einzelnen Befehl aber typischerweise in einem Taktzyklus abarbeiten. Moderne CPUs mit CISC-Befehlssatz (darunter fallen heute fast ausschließlich Intel-x86-CPUs) decodieren zur schnelleren Abarbeitung die komplexen CISC-Befehle zur Ausführung intern in eine RISC-ähnliche Mikrocontroller-Sprache.

Performance: Jeder Befehl wird in einer in Datenblättern angegebenen Anzahl von Taktzyklen des Prozessors abgearbeitet. Deren Kenntnis ermöglicht es dem Programmierer (bei extrem zeitkritischen Anwendungen) beispielsweise, Befehle mit vielen Taktzyklen durch mehrere, in der Summe aber effizientere 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. Zum Beispiel. 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.

Literatur

  • Assembler – Maschinennahes Programmieren von Anfang an. rororo Taschenbücher Nr. 61224; (2003); ISBN 3-499-61224-0

Einzelnachweise

  1. Turbo Assembler: Borland International 1988, Hrsg: Heimsoeth Software, München, 1988, Seite 523 bis 579
  2. Trutz Eyke Podschun: Das Assemblerbuch – Grundlagen, Einführung und Hochsprachenoptimierung, Addison-Wesley, 2002, Seite 557

Weblinks


Wikimedia Foundation.

Synonyme:

Schlagen Sie auch in anderen Wörterbüchern nach:

  • Maschinensprache — Ma|schi|nen|spra|che 〈f. 19; EDV〉 Programmiersprache des untersten Niveaus, d. h. in Binärcode Darstellung, deren Elemente direkt von der EDV Anlage verarbeitet werden können * * * Ma|schi|nen|spra|che, die (EDV): Programmiersprache. * * *… …   Universal-Lexikon

  • Maschinensprache — kompiuterinė kalba statusas T sritis automatika atitikmenys: angl. absolute language; actual language; computer language; machine language vok. Maschinensprache, f; Rechnersprache, f rus. машинный язык, m; язык машинных кодов, m pranc. langage… …   Automatikos terminų žodynas

  • Maschinensprache — Programmiersprache der 1. Generation, die vom Computer unmittelbar verstandene Programmsprache (vergl. > Lexikon der Text und Datenverarbeitung ) …   Acronyms

  • Maschinensprache — ⇡ Programmiersprache …   Lexikon der Economics

  • Maschinensprache — Ma|schi|nen|spra|che die; , n: Programmiersprache eines Computers (EDV) …   Das große Fremdwörterbuch

  • Maschinensprache — Programmiersprache der 1. Generation, die vom Computer unmittelbar verstandene Programmsprache (vergl. > Lexikon der Text und Datenverarbeitung ) …   Acronyms von A bis Z

  • Maschinensprache — Ma|schi|nen|spra|che …   Die deutsche Rechtschreibung

  • niedere Maschinensprache — žemojo lygmens kalba statusas T sritis automatika atitikmenys: angl. low level language vok. Grundsprache, f; niedere Maschinensprache, f rus. язык низкого уровня, m pranc. langage de bas niveau, m …   Automatikos terminų žodynas

  • Maschinenbefehl — 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… …   Deutsch Wikipedia

  • Maschinencode — 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… …   Deutsch Wikipedia