Looking for assembly Keywords? Try Ask4Keywords

Assembly LanguageErste Schritte mit Assembler


Bemerkungen

Assembly ist ein allgemeiner Name, der für viele von Menschen lesbare Formen von Maschinencode verwendet wird. Es unterscheidet sich natürlich stark zwischen verschiedenen CPUs (Central Processing Unit), aber auch auf einer einzelnen CPU können mehrere inkompatible Dialekte der Assembly vorhanden sein, die jeweils von verschiedenen Assemblern in den vom CPU-Ersteller definierten identischen Maschinencode kompiliert werden.

Wenn Sie Fragen zu Ihrem eigenen Montageproblem stellen möchten, geben Sie immer an, welche HW und welchen Assembler Sie verwenden. Andernfalls ist es schwierig, Ihre Frage ausführlich zu beantworten.

Das Lernen der Assemblierung einer einzelnen CPU hilft beim Erlernen der Grundlagen der verschiedenen CPU. Jede HW-Architektur kann jedoch erhebliche Unterschiede in den Details aufweisen, sodass das Erlernen von ASM für eine neue Plattform dem Erlernen von Grund auf nahe sein kann.

Links:

X86 Assembly Wikibook

Einführung

Assemblersprache ist eine von Menschen lesbare Form von Maschinensprache oder Maschinencode, die die tatsächliche Folge von Bits und Bytes ist, mit der die Prozessorlogik arbeitet. Im Allgemeinen ist es für Menschen einfacher, Mnemonics als Binär-, Oktal- oder Hexadezimalwörter zu lesen und zu programmieren. Daher schreiben Menschen normalerweise Code in Assembler-Sprache und konvertieren ihn dann mit einem oder mehreren Programmen in das vom Prozessor verstandene Maschinensprachenformat.

BEISPIEL:

mov eax, 4
cmp eax, 5
je point
 

Ein Assembler ist ein Programm, das das Assemblersprachenprogramm liest, analysiert und die entsprechende Maschinensprache erzeugt. Es ist wichtig zu verstehen, dass es im Gegensatz zu einer Sprache wie C ++, die eine einzige Sprache ist, die im Standarddokument definiert ist, viele verschiedene Assembler-Sprachen gibt. Jede Prozessorarchitektur, ARM, MIPS, x86 usw. hat einen anderen Maschinencode und somit eine andere Assemblersprache. Darüber hinaus gibt es manchmal mehrere verschiedene Assembly-Sprachen für dieselbe Prozessorarchitektur. Insbesondere verfügt die x86-Prozessorfamilie über zwei gängige Formate, die häufig als Gassyntax ( gas ist der Name der ausführbaren Datei für den GNU Assembler) und Intel-Syntax (nach dem Urheber der x86-Prozessorfamilie bezeichnet) bezeichnet werden. Sie sind unterschiedlich, aber äquivalent dazu, dass man in der Regel jedes Programm in einer der beiden Syntaxschreiben kann.

Im Allgemeinen dokumentiert der Erfinder des Prozessors den Prozessor und seinen Maschinencode und erstellt eine Assembler-Sprache. Es ist üblich, dass nur diese Assembler-Sprache verwendet wird. Anders als Compiler-Schreiber, die versuchen, sich an einen Sprachstandard zu halten, ist die vom Erfinder des Prozessors definierte Assemblersprache normalerweise, aber nicht immer die Version, die von den Personen verwendet wird, die Assembler schreiben .

Es gibt zwei allgemeine Arten von Prozessoren:

  • CISC (Complex Instruction Set Computer): Es gibt viele verschiedene und oft komplexe Maschinensprachenanweisungen

  • RISC (Reduced Instruction Set Computers): Im Gegensatz dazu gibt es weniger und einfachere Anweisungen

Für einen Assembler-Programmierer besteht der Unterschied darin, dass ein CISC-Prozessor sehr viele Anweisungen zum Lernen hat, aber es gibt häufig Anweisungen, die für eine bestimmte Aufgabe geeignet sind, während RISC-Prozessoren weniger und einfachere Anweisungen haben, aber für eine gegebene Operation kann der Assembler-Programmierer erforderlich sein Weitere Anweisungen schreiben, um das Gleiche zu erledigen.

Andere Programmiersprachen-Compiler erzeugen manchmal zuerst einen Assembler, der dann durch Aufrufen eines Assemblers in Maschinencode kompiliert wird. Zum Beispiel gcc eine eigene Gas Assembler in Endstadium der Kompilierung verwendet wird . Produzierter Maschinencode wird häufig in Objektdateien gespeichert, die vom Linker-Programm in eine ausführbare Datei eingebunden werden können.

Eine komplette "Toolchain" besteht häufig aus einem Compiler, Assembler und Linker. Dieser Assembler und Linker kann dann direkt zum Schreiben von Programmen in Assembler-Sprache verwendet werden. In der GNU-Welt enthält das binutils- Paket den Assembler und den Linker sowie verwandte Werkzeuge. Diejenigen, die nur an Assembler-Programmierung interessiert sind, benötigen kein gcc oder andere Compiler-Pakete.

Kleine Mikrocontroller werden oft nur in Assembler-Sprache oder in einer Kombination aus Assembler-Sprache und einer oder mehreren übergeordneten Sprachen wie C oder C ++ programmiert. Dies geschieht, weil man die speziellen Aspekte der Befehlssatzarchitektur für solche Geräte häufig verwenden kann, um kompakteren, effizienteren Code zu schreiben, als dies in einer höheren Sprache möglich wäre, und solche Geräte haben oft einen begrenzten Speicher und Register. Viele Mikroprozessoren werden in eingebetteten Systemen verwendet, bei denen es sich nicht um Universalcomputer handelt, die zufällig einen Mikroprozessor enthalten. Beispiele für solche eingebetteten Systeme sind Fernseher, Mikrowellenherde und die Motorsteuereinheit eines modernen Automobils. Viele dieser Geräte verfügen weder über eine Tastatur noch über einen Bildschirm. Daher schreibt ein Programmierer das Programm im Allgemeinen auf einem Universalcomputer und führt einen Cross-Assembler aus (so genannt, da diese Art von Assembler Code für einen anderen Prozessortyp als denjenigen erzeugt, auf dem er ausgeführt wird ) und / oder einen Cross-Compiler und Crosslinker zur Erstellung von Maschinencode.

Es gibt viele Anbieter für solche Tools, die so unterschiedlich sind wie die Prozessoren, für die sie Code erzeugen. Viele, aber nicht alle Prozessoren verfügen über eine Open-Source-Lösung wie GNU, sdcc, llvm oder andere.

X86-Assembly in Visual Studio 2015 ausführen

Schritt 1 : Erstellen Sie ein leeres Projekt über Datei -> Neues Projekt .

Geben Sie hier die Bildbeschreibung ein

Schritt 2 : Klicken Sie mit der rechten Maustaste auf die Projektlösung und wählen Sie Abhängigkeiten erstellen -> Anpassungen erstellen .

Schritt 3 : Aktivieren Sie das Kontrollkästchen ".masm" .

Geben Sie hier die Bildbeschreibung ein

Schritt 4 : Drücken Sie die Taste "ok" .

Schritt 5 : Erstellen Sie Ihre Baugruppendatei und geben Sie Folgendes ein:

.386
    .model small
        .code

            public main
                main proc

                    ; Ends program normally

                    ret
                main endp
            end main
 

Schritt 6 : Kompilieren!

Geben Sie hier die Bildbeschreibung ein

Hallo Welt für Linux x86_64 (Intel 64 Bit)

section .data
    msg db "Hello world!",10      ; 10 is the ASCII code for a new line (LF)

section .text
    global _start

_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, msg
    mov rdx, 13
    syscall
    
    mov rax, 60
    mov rdi, 0
    syscall
 

Wenn Sie dieses Programm ausführen möchten, benötigen Sie zunächst den Netwide Assembler , nasm , da dieser Code seine Syntax verwendet. Verwenden Sie dann die folgenden Befehle (vorausgesetzt, der Code befindet sich in der Datei helloworld.asm ). Sie werden zum Zusammenbauen, Verknüpfen bzw. Ausführen benötigt.

  • nasm -felf64 helloworld.asm
  • ld helloworld.o -o helloworld
  • ./helloworld

Der Code verwendet den sys_write von Linux. Hier sehen Sie eine Liste aller Syscalls für die x86_64-Architektur. Wenn Sie auch die man-Seiten von write and exit berücksichtigen, können Sie das obige Programm in eine C-Datei übersetzen, die dasselbe tut und viel lesbarer ist:

#include <unistd.h>

#define STDOUT 1

int main()
{
    write(STDOUT, "Hello world!\n", 13);
    _exit(0);
}
 

Hier sind nur zwei Befehle zum Kompilieren und Verknüpfen (erster Befehl) und Ausführen erforderlich:

  • gcc helloworld_c.c -o helloworld_c .
  • ./helloworld_c

Hallo Welt für OS X (x86_64, Intel-Syntaxgas)

.intel_syntax noprefix

.data

.align 16
hello_msg:
    .asciz "Hello, World!"

.text

.global _main
_main:
    push rbp
    mov rbp, rsp

    lea rdi, [rip+hello_msg]
    call _puts

    xor rax, rax
    leave
    ret
 

Montieren:

clang main.s -o hello
./hello
 

Anmerkungen:

  • Von der Verwendung von Systemaufrufen wird abgeraten, da die Systemaufruf-API in OS X nicht als stabil gilt. Verwenden Sie stattdessen die C-Bibliothek. ( Verweis auf eine Stack Overflow-Frage )
  • Intel empfiehlt, dass Strukturen, die größer als ein Wort sind, an einer 16-Byte-Grenze beginnen. ( Verweis auf Intel-Dokumentation )
  • Die Ordnungsdaten werden über die Register an Funktionen übergeben: rdi, rsi, rdx, rcx, r8 und r9. ( Verweis auf System V ABI )

Maschinensprache

Maschinencode ist die Bezeichnung für die Daten in einem nativen Maschinenformat, das direkt von der Maschine verarbeitet wird - in der Regel vom Prozessor, der als CPU (Central Processing Unit) bezeichnet wird.

Übliche Computerarchitekturen ( von-Neumann-Architekturen ) bestehen aus einem Universalprozessor (CPU), einem Allzweckspeicher, der sowohl Programm (ROM / RAM) als auch verarbeitete Daten und Eingabe- und Ausgabegeräte (E / A-Geräte) speichert.

Der Hauptvorteil dieser Architektur ist die relative Einfachheit und Universalität der einzelnen Komponenten - im Vergleich zu früheren Computercomputern (mit fest verdrahtetem Programm beim Maschinenkonzept) oder konkurrierenden Architekturen (z. B. die Harvard-Architektur , die den Speicher des Programms vom Speicher des Computers trennt Daten). Nachteil ist eine etwas schlechtere allgemeine Leistung. Auf lange Sicht erlaubte die Universalität eine flexible Nutzung, die in der Regel die Leistungskosten aufwog.

Wie hängt das mit dem Maschinencode zusammen?

Programm und Daten werden auf diesen Computern als Nummern im Speicher abgelegt. Es gibt keine echte Möglichkeit, Code von Daten zu unterscheiden. Daher geben Betriebssysteme und Maschinenbediener Hinweise an die CPU. An diesem Eintrittspunkt des Speichers wird das Programm gestartet, nachdem alle Zahlen in den Speicher geladen wurden. Die CPU liest dann die am Einsprungspunkt gespeicherte Anweisung (Nummer) und verarbeitet sie rigoros, wobei sie die nächsten Zahlen als weitere Anweisungen sequentiell liest, es sei denn, das Programm selbst fordert die CPU selbst auf, die Ausführung an anderer Stelle fortzusetzen.

Zum Beispiel sind zwei 8-Bit-Nummern (8 zusammengruppierte Bits entsprechen 1 Byte, das ist eine vorzeichenlose Ganzzahl im Bereich von 0 bis 255): 60 201 , wenn sie als Code auf der Zilog Z80-CPU ausgeführt wird, werden sie als zwei Anweisungen verarbeitet: INC a (Inkrementieren des Wertes in Register a um eins) und RET (Rückkehr von der Subroutine, Zeigen der CPU, um Anweisungen aus verschiedenen Teilen des Speichers auszuführen).

Um dieses Programm zu definieren, kann ein Mensch diese Zahlen von einem Speicher- / Datei-Editor eingeben, zum Beispiel im Hex-Editor als zwei Bytes: 3C C9 (Dezimalzahlen 60 und 201 in Basis-16-Kodierung geschrieben). Das wäre Programmierung in Maschinencode .

Um die CPU-Programmierung für Menschen einfacher zu gestalten, wurden Assembler- Programme erstellt, die Textdateien lesen können, die Folgendes enthalten:

subroutineIncrementA:
    INC   a
    RET

dataValueDefinedInAssemblerSource:
    DB    60          ; define byte with value 60 right after the ret
 

Ausgabe der Byte-Hex-Zahlen-Folge 3C C9 3C , umwickelt mit optionalen zusätzlichen Zahlen, die für die Zielplattform spezifisch sind: Markieren, welcher Teil einer solchen Binärdatei ausführbarer Code ist, wobei der Eintrittspunkt für das Programm (der erste Befehl davon) ist, welche Teile codiert sind Daten (nicht ausführbar) usw.

Beachten Sie, dass der Programmierer das letzte Byte mit dem Wert 60 als "Daten" angegeben hat, aber aus CPU-Sicht unterscheidet es sich in keiner Weise von INC a Byte. Es ist Sache des ausführenden Programms, die CPU über als Anweisungen vorbereitete Bytes korrekt zu navigieren und Datenbytes nur als Anweisungen für Anweisungen zu verarbeiten.

Eine solche Ausgabe wird normalerweise in einer Datei auf einem Speichergerät gespeichert, die später von OS ( Betriebssystem - ein Maschinencode, der bereits auf dem Computer ausgeführt wird, um die Verarbeitung mit dem Computer zu erleichtern) in den Speicher geladen wird, bevor er ausgeführt wird, und schließlich die CPU auf den Computer zeigt Einstiegspunkt des Programms.

Die CPU kann nur Maschinencode verarbeiten und ausführen. Jeder Speicherinhalt, auch zufälliger, kann als solcher verarbeitet werden. Das Ergebnis kann jedoch zufällig sein und vom " Absturz " erkannt und vom Betriebssystem gehandhabt werden. O-Geräte oder Schäden an empfindlichen Geräten, die an den Computer angeschlossen sind (kein üblicher Fall für Heimcomputer :)).

Dem ähnlichen Prozess folgen viele andere Programmiersprachen auf hoher Ebene, die die Quelle (vom Menschen lesbare Textform des Programms) zu Zahlen zusammenfassen, die entweder den Maschinencode (native Anweisungen der CPU) oder im Fall von interpretierten / hybriden Sprachen in einigen allgemeinen Formaten darstellen sprachspezifischer Code für virtuelle Maschinen, der während der Ausführung durch Interpreter oder virtuelle Maschine in nativen Maschinencode dekodiert wird.

Einige Compiler verwenden den Assembler als Zwischenstufe der Kompilierung. Sie konvertieren die Quelle zuerst in das Assembler-Formular und führen dann das Assembler-Tool aus, um den endgültigen Maschinencode zu erhalten (GCC-Beispiel: Führen Sie gcc -S helloworld.c , um eine Assembler-Version des C-Programms zu erhalten helloworld.c ).