Armanweisungen. Untersuchung des Befehlssatzes des ARM-Prozessors. Bedingte Befehlsausführung

Hallo an alle!
Von Beruf bin ich Java-Programmierer. Die letzten Arbeitsmonate haben mich dazu gebracht, mich mit der Entwicklung für das Android NDK vertraut zu machen und dementsprechend native Anwendungen in C zu schreiben. Hier stieß ich auf das Problem der Optimierung von Linux-Bibliotheken. Viele stellten sich als absolut nicht für ARM optimiert heraus und belasteten den Prozessor stark. Früher habe ich praktisch nicht in Assembler programmiert, daher war es zunächst schwierig, mit dem Erlernen dieser Sprache zu beginnen, aber ich habe mich dennoch entschlossen, es zu versuchen. Dieser Artikel ist sozusagen von einem Anfänger für Anfänger geschrieben. Ich werde versuchen, die Grundlagen zu beschreiben, die ich bereits studiert habe, ich hoffe, es wird jemanden interessieren. Außerdem freue ich mich über konstruktive Kritik von Fachleuten.

Einführung
Lassen Sie uns zunächst herausfinden, was ARM ist. Wikipedia gibt diese Definition:

Die ARM-Architektur (Advanced RISC Machine, Acorn RISC Machine, Advanced RISC Machine) ist eine Familie lizenzierter 32-Bit- und 64-Bit-Mikroprozessorkerne, die von ARM Limited entwickelt wurden. Das Unternehmen beschäftigt sich ausschließlich mit der Entwicklung von Kerneln und Tools für diese (Compiler, Debugging-Tools usw.) und verdient an der Lizenzierung der Architektur an Dritthersteller.

Wenn jemand es nicht weiß, werden jetzt die meisten mobilen Geräte, Tablets, auf dieser speziellen Prozessorarchitektur entwickelt. Der Hauptvorteil dieser Familie ist ihr geringer Stromverbrauch, aufgrund dessen sie häufig in verschiedenen eingebetteten Systemen verwendet wird. Die Architektur hat sich im Laufe der Zeit weiterentwickelt, und seit ARMv7 wurden 3 Profile definiert: „A“ (Anwendung) – Anwendungen, „R“ (Echtzeit) – in Echtzeit, „M“ (Mikrocontroller) – Mikrocontroller. Die Entwicklungsgeschichte dieser Technologie und weitere interessante Daten können Sie auf Wikipedia nachlesen oder im Internet googeln. ARM unterstützt verschiedene Betriebsmodi (Thumb und ARM, außerdem ist kürzlich Thumb-2 erschienen, das eine Mischung aus ARM und Thumb ist). In diesem Artikel betrachten wir den ARM-Modus selbst, in dem ein 32-Bit-Befehlssatz ausgeführt wird.

Jeder ARM-Prozessor besteht aus den folgenden Blöcken:

  • 37 Register (von denen während der Entwicklung nur 17 sichtbar sind)
  • Arithmetische Logikeinheit (ALU) - führt arithmetische und logische Aufgaben aus
  • Barrel Shifter - ein Gerät zum Verschieben von Datenblöcken um eine bestimmte Anzahl von Bits
  • Das CP15 - ein spezielles System, das ARM-Coprozessoren steuert
  • Befehlsdecodierer - befasst sich mit der Umwandlung eines Befehls in eine Folge von Mikrooperationen
Dies sind nicht alle Komponenten von ARM, aber das Vertiefen in den Dschungel des Bauens von Prozessoren ist nicht Teil des Themas dieses Artikels.
Pipeline-Ausführung
ARM-Prozessoren verwenden eine dreistufige Pipeline (seit ARM8 wurde eine fünfstufige Pipeline implementiert). Betrachten wir eine einfache Pipeline am Beispiel des ARM7TDMI-Prozessors. Die Ausführung jeder Anweisung besteht aus drei Schritten:

1. Probenahmephase (F)
In diesem Stadium fließen die Anweisungen vom RAM zur Prozessorpipeline.
2. Dekodierungsschritt (D)
Anweisungen werden dekodiert und ihr Typ wird erkannt.
3. Ausführungsstufe (E)
Die Daten werden in die ALU eingegeben und ausgeführt, und der resultierende Wert wird in das angegebene Register geschrieben.

Bei der Entwicklung müssen Sie jedoch berücksichtigen, dass es Anweisungen gibt, die mehrere Ausführungszyklen verwenden, z. B. Laden (LDR) oder Speichern. In einem solchen Fall wird die Ausführungsphase (E) in Phasen (E1, E2, E3...) unterteilt.

Bedingte Ausführung
Eine der wichtigsten Funktionen des ARM-Assemblers ist die bedingte Ausführung. Jede Anweisung kann bedingt ausgeführt werden und dafür werden Suffixe verwendet. Wenn dem Namen einer Anweisung ein Suffix hinzugefügt wird, werden die Parameter überprüft, bevor sie ausgeführt wird. Wenn die Parameter nicht mit der Bedingung übereinstimmen, wird die Anweisung nicht ausgeführt. Suffixe:
MI - negative Zahl
PL - positiv oder null
AL - Anweisung immer ausführen
Es gibt viele weitere bedingte Ausführungssuffixe. Lesen Sie den Rest der Suffixe und Beispiele in der offiziellen Dokumentation: ARM-Dokumentation
Jetzt heißt es überlegen...
Grundlagen der ARM-Assembler-Syntax
Für diejenigen, die zuvor mit Assembler gearbeitet haben, kann dieser Punkt tatsächlich übersprungen werden. Für alle anderen werde ich die Grundlagen der Arbeit mit dieser Sprache beschreiben. Jedes Assemblerprogramm besteht also aus Anweisungen. Die Anweisung wird wie folgt erstellt:
(Marke) (Befehl|Operanden) (@ Kommentar)
Das Label ist ein optionaler Parameter. Eine Anweisung ist direkt ein Mnemonik für eine Anweisung an den Prozessor. Die grundlegenden Anweisungen und ihre Verwendung werden als nächstes besprochen. Operanden - Konstanten, Registeradressen, Adressen im RAM. Kommentar ist ein optionaler Parameter, der die Ausführung des Programms nicht beeinflusst.
Namen registrieren
Folgende Registernamen sind erlaubt:
1.r0-r15

3.v1-v8 (variable Register, r4 bis r11)

4.sb und SB (statisches Register, r9)

5.sl und SL (r10)

6.fp und FP (r11)

7.ip und IP (r12)

8.sp und SP (r13)

9.lr und LR (r14)

10.pc und PC (Programmzähler, r15).

Variablen und Konstanten
In ARM-Assembler können wie in jeder (fast) anderen Programmiersprache Variablen und Konstanten verwendet werden. Sie werden in folgende Typen unterteilt:
  • Numerisch
  • Rätsel
  • Schnur
Numerische Variablen werden wie folgt initialisiert:
ein SETA 100; wird eine numerische Variable „a“ mit dem Wert 100 angelegt.
String-Variablen:
Improb SETS "literal"; Die Improb-Variable wird mit dem Wert "literal" erstellt. AUFMERKSAMKEIT! Der Variablenwert darf 5120 Zeichen nicht überschreiten.
Boolesche Variablen verwenden die Werte TRUE bzw. FALSE.
Beispiele für ARM-Assembler-Anweisungen
In dieser Tabelle habe ich die wichtigsten Anweisungen zusammengestellt, die für die weitere Entwicklung erforderlich sind (in der grundlegendsten Phase :):

Um die Verwendung grundlegender Anweisungen zu vertiefen, schreiben wir einige einfache Beispiele, aber zuerst brauchen wir eine Arm-Toolchain. Ich arbeite unter Linux, also habe ich mich für frank.harvard.edu/~coldwell/toolchain (arm-unknown-linux-gnu toolchain) entschieden. Es ist so einfach wie das Schälen von Birnen, wie jedes andere Programm unter Linux. In meinem Fall (russisches Fedora) musste ich nur RPM-Pakete von der Seite installieren.
Jetzt ist es an der Zeit, das einfachste Beispiel zu schreiben. Das Programm wird absolut nutzlos sein, aber die Hauptsache ist, dass es funktioniert :) Hier ist der Code, den ich Ihnen anbiete:
start: @ Ein optionaler String, der den Beginn des Programms angibt mov r0, #3 @ Lade den Wert 3 in Register r0 mov r1, #2 @ Mach das gleiche mit Register r1, nur jetzt mit dem Wert 2 füge r2, r1, r0 hinzu @ Addieren Sie die Werte von r0 und r1, schreiben Sie die Antwort auf r2 mul r3, r1, r0 @ Multiplizieren Sie den Wert von Register r1 mit dem Wert von Register r0, schreiben Sie die Antwort auf r3 stop: b stop @ Programmabschlusszeile
Wir kompilieren das Programm, bis wir die .bin-Datei erhalten:
/usr/arm/bin/arm-unknown-linux-gnu-as -o arm.o arm.s /usr/arm/bin/arm-unknown-linux-gnu-ld -Ttext=0x0 -o ​​arm. elf arm .o /usr/arm/bin/arm-unknown-linux-gnu-objcopy -O binär arm.elf arm.bin
(Der Code befindet sich in der Datei arm.s und die Toolchain befindet sich in meinem Fall im Verzeichnis /usr/arm/bin/)
Wenn alles gut gelaufen ist, haben Sie 3 Dateien: arm.s (der eigentliche Code), arm.o, arm.elf, arm.bin (das eigentliche ausführbare Programm). Um die Funktionsweise des Programms zu testen, ist kein eigenes Armgerät erforderlich. Es reicht aus, QEMU zu installieren. Als Referenz:

QEMU ist ein kostenloses Open-Source-Programm zum Emulieren von Hardware verschiedener Plattformen.

Beinhaltet die Emulation von Intel x86-Prozessoren und E/A-Geräten. Kann 80386, 80486, Pentium, Pentium Pro, AMD64 und andere x86-kompatible Prozessoren emulieren; PowerPC, ARM, MIPS, SPARC, SPARC64, m68k - nur teilweise.

Läuft auf Syllable, FreeBSD, FreeDOS, Linux, Windows 9x, Windows 2000, Mac OS X, QNX, Android und mehr.

Um arm zu emulieren, benötigen Sie also qemu-system-arm. Dieses Paket ist in yum, wenn Sie also Fedora haben, können Sie den Ärger überspringen und einfach den Befehl ausführen:
yum installiere qemu-system-arm

Als nächstes müssen wir den ARM-Emulator starten, damit er unser arm.bin-Programm ausführt. Dazu erstellen wir eine flash.bin-Datei, die der Flash-Speicher für QEMU sein wird. Das geht ganz einfach:
dd if=/dev/zero of=flash.bin bs=4096 count=4096 dd if=arm.bin of=flash.bin bs=4096 conv=notrunc
Nun laden wir QEMU mit dem erhaltenen Flash-Speicher:
qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
Bei der Ausgabe erhalten Sie so etwas:

$ qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
QEMU 0.15.1 monitor - geben Sie "help" ein, um weitere Informationen zu erhalten
(qemu)

Unser arm.bin-Programm musste die Werte von vier Registern ändern, also schauen wir uns diese Register an, um den korrekten Betrieb zu überprüfen. Dies geschieht mit einem sehr einfachen Befehl: info registers
Am Ausgang sehen Sie alle 15 ARM-Register, und vier davon haben geänderte Werte. Überprüfen Sie:) Die Registerwerte sind die gleichen, wie Sie es erwarten würden, nachdem das Programm ausgeführt wurde:
(QEMU) Info-Registrister R00 = 00000003 R01 = 00000002 R02 = 00000005 R03 = 00000006 R04 = 00000000 R05 = 000000 R06 = 000000 R07 = 000000 R08 = 000000 R09 = 00000000 R10 = 000000 R100 = 00000000 R12 = 00000000 R13 = 00000000 R14 = 00000000 R15=00000010 PSR=400001d3 -Z-- Ein svc32

P.S. In diesem Artikel habe ich versucht, die Grundlagen der Programmierung in ARM-Assembler zu beschreiben. Ich hoffe, es hat euch gefallen! Das reicht aus, um weiter in den Dschungel dieser Sprache einzutauchen und darin Programme zu schreiben. Wenn alles klappt, werde ich weiter darüber schreiben, was ich selbst gelernt habe. Wenn es Fehler gibt, treten Sie bitte nicht, da ich neu in Assembler bin.

Wenn Sie die Raspbian-Distribution als Betriebssystem Ihres Raspberry Pi verwenden, benötigen Sie zwei Dienstprogramme, nämlich as (ein Assembler, der Assembler-Quellcode in Binärcode konvertiert) und ld (ein Linker, der die resultierende ausführbare Datei erstellt). Beide Dienstprogramme sind im Softwarepaket binutils enthalten, sodass sie sich möglicherweise bereits auf Ihrem System befinden. Natürlich benötigen Sie auch einen guten Texteditor; Ich empfehle immer, Vim für die Programmentwicklung zu verwenden, aber es hat eine hohe Eintrittsbarriere, also ist Nano oder jeder andere GUI-Texteditor auch in Ordnung.

Bereit zum Start? Kopieren Sie den folgenden Code und speichern Sie ihn in der Datei myfirst.s:

Global _start _start: mov r7, #4 mov r0, #1 ldr r1, =string mov r2, #stringlen swi 0 mov r7, #1 swi 0 .data string: .ascii "Ciao!\n" stringlen = . -Zeichenfolge

Dieses Programm druckt einfach die Zeichenfolge "Ciao!" auf dem Bildschirm, und wenn Sie Artikel über die Verwendung von Assemblersprache für die Arbeit mit x86-CPUs gelesen haben, sind Ihnen einige der verwendeten Anweisungen möglicherweise bekannt. Dennoch gibt es viele Unterschiede zwischen den x86- und ARM-Architekturanweisungen, die sich auch in der Syntax des Quellcodes ausdrücken lassen, sodass wir sie im Detail analysieren werden.

Aber vorher sollte erwähnt werden, dass Sie den folgenden Befehl verwenden müssen, um den obigen Code zu assemblieren und die resultierende Objektdatei in eine ausführbare Datei zu verknüpfen:

Als -o meineerste.o meineerste.s && ld -o meineerste meineerste.o

Jetzt können Sie das generierte Programm mit dem Befehl ./myfirst ausführen. Sie haben vielleicht bemerkt, dass die ausführbare Datei eine sehr bescheidene Größe von etwa 900 Bytes hat – wenn Sie die Programmiersprache C und die Funktion puts() verwenden würden, wäre die Größe der Binärdatei etwa fünfmal größer!

Erstellen Sie Ihr eigenes Betriebssystem für Raspberry Pi

Wenn Sie die vorherigen Artikel in der Serie über x86-Assemblersprachenprogrammierung gelesen haben, erinnern Sie sich wahrscheinlich an das erste Mal, als Sie Ihr eigenes Betriebssystem ausgeführt haben, das ohne die Hilfe von Linux oder einem anderen Betriebssystem eine Meldung auf dem Bildschirm anzeigte. Danach haben wir es verbessert, indem wir eine einfache Befehlszeilenschnittstelle und einen Mechanismus zum Laden und Ausführen von Programmen von der Festplatte hinzugefügt haben, um eine Reserve für die Zukunft zu lassen. Es war eine sehr interessante, aber nicht sehr schwierige Aufgabe, hauptsächlich dank der Hilfe der BIOS-Firmware - sie bot eine vereinfachte Schnittstelle für den Zugriff auf Bildschirm, Tastatur und Diskettenlaufwerk.

Beim Raspberry Pi stehen Ihnen dann keine sinnvollen BIOS-Funktionen mehr zur Verfügung, sodass Sie selbst Gerätetreiber entwickeln müssen, was an sich eine schwierige und uninteressante Aufgabe ist, verglichen mit dem Zeichnen auf dem Bildschirm und der Implementierung des Mechanismus für Ausführen eigener Programme. Gleichzeitig gibt es im Netzwerk mehrere Anleitungen, die die Anfangsphasen des Raspberry Pi-Startvorgangs, die Funktionen des Mechanismus für den Zugriff auf GPIO-Pins usw. ausführlich beschreiben.

Eines der besten Dokumente dieser Art ist Baking Pi (www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/index.html) von der University of Cambridge. Im Wesentlichen handelt es sich um eine Reihe von Handbüchern, die beschreiben, wie man mit Assemblersprache arbeitet, um LEDs einzuschalten, auf Pixel auf dem Bildschirm zuzugreifen, Tastatureingaben zu empfangen und so weiter. Beim Lesen erfahren Sie viel über die Raspberry Pi-Hardware, und die Handbücher wurden für die Originalmodelle dieser Einplatinencomputer geschrieben, sodass es keine Garantie dafür gibt, dass sie für Modelle wie A+, B+ und Pi 2 relevant sind.

Wenn Sie die Programmiersprache C bevorzugen, sollten Sie auf das Dokument der Valvers-Ressource verweisen, das sich unter http://tinyurl.com/qa2s9bg befindet und eine Beschreibung des Prozesses zum Einrichten eines Cross-Compilers und Erstellen eines einfachen Betriebssystemkerns enthält. Darüber hinaus finden Sie im Wiki-Abschnitt der nützlichen OSDev-Ressource unter http://wiki.osdev.org/Raspberry_Pi_Bare_Bones Informationen zum Erstellen und Ausführen eines grundlegenden Betriebssystemkerns auf einem Raspberry Pi.

Wie oben erwähnt, ist das größte Problem in diesem Fall die Notwendigkeit, Treiber für verschiedene Raspberry Pi-Hardwaregeräte zu entwickeln: USB-Controller, SD-Kartensteckplatz und so weiter. Schließlich kann selbst der Code für die genannten Geräte Zehntausende von Zeilen umfassen. Wer dennoch ein voll funktionsfähiges Betriebssystem für den Raspberry Pi selbst entwickeln möchte, sollte die Foren auf www.osdev.org besuchen und nachfragen, ob schon jemand Treiber für diese Geräte entwickelt und diese möglichst an den Kernel von angepasst hat Ihres Betriebssystems und sparen dadurch viel Zeit.

Wie das alles funktioniert

Die ersten beiden Codezeilen sind keine CPU-Anweisungen, sondern Assembler- und Linker-Direktiven. Jedes Programm muss einen klar definierten Einstiegspunkt namens _start haben, und in unserem Fall landete er ganz am Anfang des Codes. Somit teilen wir dem Linker mit, dass die Ausführung des Codes mit der allerersten Anweisung beginnen soll und keine zusätzlichen Aktionen erforderlich sind.

Mit der folgenden Anweisung schreiben wir die Zahl 4 in das Register r7 . (Wenn Sie noch nie mit Assembler gearbeitet haben, sollten Sie wissen, dass ein Register ein Speicherort ist, der sich direkt auf der CPU befindet. Die meisten modernen CPUs implementieren eine kleine Anzahl von Registern im Vergleich zu den Millionen oder Milliarden von RAM-Zellen, aber die Register sind unverzichtbar, da sie viel schneller arbeiten.) ARM-Architektur-Chips bieten Entwicklern eine große Anzahl von Universalregistern: Ein Designer kann bis zu 16 Register verwenden, die von r0 bis r15 benannt sind, und diese Register sind nicht mit historisch etablierten Einschränkungen verbunden , wie im Fall der Architektur x86, wo einige der Register zu bestimmten Zeiten für bestimmte Zwecke verwendet werden können.

Obwohl die mov-Anweisung der gleichnamigen x86-Architektur-Anweisung sehr ähnlich ist, sollten Sie auf jeden Fall auf das Raute-Symbol neben der Zahl 4 achten, das darauf hinweist, dass als nächstes ein ganzzahliger Wert und keine Speicheradresse steht . In diesem Fall möchten wir den Systemaufruf write des Linux-Kernels verwenden, um unseren String auszugeben; Um Systemaufrufe verwenden zu können, müssen Sie die Register mit den erforderlichen Werten füllen, bevor Sie dem Kernel seine Arbeit verzeihen. Die Systemrufnummer muss in Register r7 passen, wobei 4 die Nummer des schreibenden Systemaufrufs ist.

Mit der folgenden mov-Anweisung platzieren wir den Dateideskriptor, in dem der String „Ciao!“, also der Standardausgabestrom-Deskriptor, geschrieben werden soll, in das Register r0 . Da in diesem Fall der Standardausgabestrom verwendet wird, wird sein Standarddeskriptor, also 1 , in das Register gestellt. Als nächstes müssen wir die Adresse des Strings, den wir ausgeben wollen, in das Register r1 schreiben, indem wir die ldr-Anweisung verwenden (die „load into register“-Anweisung; beachten Sie das Gleichheitszeichen, um anzuzeigen, dass das Folgende ein Label und keine Adresse ist). Am Ende des Codes, nämlich im Datenteil, deklarieren wir diesen String in Form einer ASCII-Zeichenfolge. Um den „write“-Systemaufruf erfolgreich zu verwenden, müssen wir dem Betriebssystem-Stem-Kernel auch mitteilen, wie lang der Ausgabe-String ist, also setzen wir den stringlen-Wert in das r2-Register. (Der stringlen-Wert wird berechnet, indem die Endadresse der Zeichenfolge von der Startadresse subtrahiert wird.)

An diesem Punkt haben wir alle Register mit den notwendigen Daten gefüllt und sind bereit, die Kontrolle an den Linux-Kernel zu übertragen. Dazu nutzen wir den swi-Befehl, dessen Name für „Software Interrupt“ steht, der in den Kernel-Space des Betriebssystems transferiert (ähnlich wie der int-Befehl in den Artikeln zur x86-Architektur). Der OS-Kernel untersucht den Inhalt von Register r7, findet darin den ganzzahligen Wert 4 und folgert: „Also, das aufrufende Programm möchte einen String ausgeben.“ Danach untersucht es den Inhalt anderer Register, gibt einen String aus und gibt die Kontrolle an unser Programm zurück.

So sehen wir auf dem Bildschirm die Zeile „Ciao!“, wonach wir nur noch die Ausführung des Programms korrekt abschließen müssen. Wir lösen dieses Problem, indem wir die Aufrufnummer des Exit-Systems in das Register r7 stellen und dann die Software-Interrupt-Befehlsnummer Null aufrufen. Und das ist alles - der Betriebssystemkern schließt die Ausführung unseres Programms ab und wir wechseln wieder zur Befehlsshell.

Vim (links) ist ein hervorragender Texteditor zum Schreiben von Code in Assemblersprache – eine Datei zum Hervorheben der Syntax in Assemblersprache für die ARM-Architektur ist unter http://tinyurl.com/psdvjen verfügbar.

Beratung: Wenn Sie mit Assemblersprache arbeiten, sollten Sie nicht an Kommentaren sparen. Wir haben in diesem Artikel nicht viele Kommentare verwendet, um den Code auf den Seiten des Magazins so klein wie möglich zu halten (und auch, weil wir den Zweck jeder der Anweisungen ausführlich beschrieben haben). Doch bei der Entwicklung komplexer Programme, deren Code auf den ersten Blick selbstverständlich erscheint, sollte man sich immer Gedanken machen, wie es aussehen wird, wenn man die Assemblersprachen-Syntax für die ARM-Architektur teilweise vergisst und nach einigen Monaten wieder in die Entwicklung zurückkehrt. Sie können alle im Code verwendeten Tricks und Abkürzungen vergessen, danach sieht der Code wie kompletter Kauderwelsch aus. Basierend auf dem oben Gesagten sollten Sie dem Code so viele Kommentare wie möglich hinzufügen, auch wenn einige davon im Moment zu offensichtlich erscheinen!

Reverse-Engineering

Das Konvertieren einer Binärdatei in Assemblersprachencode kann in einigen Fällen ebenfalls nützlich sein. Das Ergebnis dieser Operation ist normalerweise kein sehr wohlgeformter Code ohne lesbare Label-Namen und Kommentare, die dennoch nützlich sein können, um die Transformationen zu studieren, die der Assembler an Ihrem Code vorgenommen hat. Um die myfirst-Binärdatei zu disassemblieren, führen Sie einfach den folgenden Befehl aus:

Objdump -d myfirst

Mit diesem Befehl können Sie den ausführbaren Codeabschnitt der Binärdatei disassemblieren (aber nicht den Datenabschnitt, da er ASCII-Text enthält). Wenn Sie sich den zerlegten Code ansehen, werden Sie feststellen, dass die Anweisungen darin fast identisch mit den Anweisungen im ursprünglichen Code sind. Disassembler werden hauptsächlich verwendet, wenn Sie das Verhalten eines Programms untersuchen müssen, das nur in Binärform verfügbar ist, z. B. ein Virus oder ein einfaches Closed-Source-Programm, dessen Verhalten Sie emulieren möchten. Gleichzeitig sollten Sie immer an die Einschränkungen denken, die der Autor des zu studierenden Programms auferlegt hat! Eine Programmbinärdatei zu disassemblieren und den resultierenden Code einfach in den Code Ihres Projekts zu kopieren, ist natürlich eine schlechte Idee; Gleichzeitig können Sie den resultierenden Code verwenden, um das Prinzip des Programms zu studieren.

Subroutinen, Schleifen und bedingte Anweisungen

Nachdem wir nun wissen, wie man einfache Programme entwirft, assembliert und verknüpft, wollen wir uns etwas Komplexerem zuwenden. Das folgende Programm verwendet Subroutinen, um Strings auszugeben (dank ihnen können wir Codefragmente wiederverwenden und sparen uns, dieselbe Art von Operationen zum Füllen von Registern mit Daten durchführen zu müssen). Dieses Programm implementiert eine Hauptereignisschleife, die die Ausgabe einer Zeichenfolge ermöglicht, bis der Benutzer "q" eingibt. Studieren Sie den Code und versuchen Sie, den Zweck der Anweisungen zu verstehen (oder zu erraten!), aber lassen Sie sich nicht entmutigen, wenn Sie etwas nicht verstehen, denn etwas später werden wir uns auch sehr ausführlich damit befassen. Beachten Sie, dass die @-Symbole in der Assemblersprache für die ARM-Architektur Kommentare hervorheben.

global _start _start: ldr r1, =string1 mov r2, #string1len bl print_string loop: mov r7, #3 @ read mov r0, #0 @ stdin ldr r1, =char mov r2, #2 @ zwei Zeichen swi 0 ldr r1, =char ldrb r2, cmp r2, #113 @ ASCII-Code des Zeichens "q" beq done ldr r1, =string2 mov r2, #string2len bl print_string b loop done: mov r7, #1 swi 0 print_string: mov r7, #4 mov r0, #1 swi 0 bx lr .data string1: .ascii "Geben Sie q zum Beenden ein!\n" string1len = . - string1 string2: .ascii "Das war nicht q...\n" string2len = . - string2 char: .word 0

Unser Programm beginnt damit, dass es einen Zeiger auf den Beginn einer Zeichenkette und einen Wert für ihre Länge in den entsprechenden Registern platziert, damit der Write-Systemaufruf folgt, unmittelbar danach springt es zur Unterroutine print_string weiter unten im Code. Um diesen Übergang zu implementieren, wird die Anweisung bl verwendet, deren Name für "Branch and Link" ("Verzweigung mit Erhalt der Adresse") steht und die selbst die aktuelle Adresse im Code speichert, mit dem Sie zurückkehren können später mit der bx-Anweisung. Die Unterroutine print_string füllt einfach die anderen Register für den Write-Systemaufruf auf die gleiche Weise wie in unserem ersten Programm, bevor sie in den Kernel-Space springt und dann mit der bx-Anweisung zur gespeicherten Codeadresse zurückkehrt.

Wenn wir zum aufrufenden Code zurückkehren, finden wir ein Label namens loop - der Name des Labels deutet bereits an, dass wir nach einer Weile darauf zurückkommen werden. Aber zuerst verwenden wir einen anderen Systemaufruf namens read (mit der Nummer 3), um das vom Benutzer über die Tastatur eingegebene Zeichen zu lesen. Daher setzen wir den Wert 3 in Register r7 und den Wert 0 (Standardeingabe-Handle) in Register r0 , da wir Benutzereingaben lesen müssen, keine Daten aus einer Datei.

Als nächstes platzieren wir die Adresse, an der wir das vom OS-Kernel gelesene und platzierte Zeichen speichern möchten, in Register r1 – in unserem Fall ist dies der am Ende des Datenabschnitts beschriebene Char-Speicherbereich. (Eigentlich brauchen wir ein Maschinenwort, also einen Speicherbereich zum Speichern von zwei Zeichen, weil darin auch der Enter-Tastencode gespeichert wird. Wenn Sie mit Assembler arbeiten, ist es wichtig, immer an die Möglichkeit des Überlaufens von Speicherbereichen zu denken, weil es gibt keine High-Level-Mechanismen, die bereit sind, Ihnen zu helfen!).

Wenn wir zum Hauptcode zurückkehren, werden wir sehen, dass der Wert 2 im r2-Register abgelegt wird, was den beiden Zeichen entspricht, die wir speichern möchten, wonach der Übergang zum Betriebssystem-Kernel-Space durchgeführt wird, um die Leseoperation durchzuführen. Der Benutzer gibt ein Zeichen ein und drückt die Eingabetaste. Jetzt müssen wir überprüfen, was dieses Zeichen ist: Wir schreiben die Adresse des Speicherbereichs (char im Datenabschnitt) in das Register r1 , danach verwenden wir die ldrb -Anweisung, um ein Byte aus dem Speicherbereich zu laden, auf den der Wert von zeigt dieses Register.

Die eckigen Klammern weisen in diesem Fall darauf hin, dass die Daten im für uns interessanten Speicherbereich und nicht im Register selbst gespeichert werden. Somit enthält das Register r2 nun ein einzelnes Zeichen aus dem Char-Speicherbereich des Datenabschnitts, und dies ist genau das Zeichen, das der Benutzer eingegeben hat. Unsere nächste Aufgabe besteht darin, den Inhalt von Register r2 mit dem Zeichen „q“ zu vergleichen, das Zeichen 113 der ASCII-Tabelle ist (siehe Zeichentabelle unter www.asciichart.com). Wir verwenden jetzt die cmp-Anweisung, um die Vergleichsoperation durchzuführen, und verwenden dann die beq-Anweisung, deren Name für „branch if equal“ (zweigen, wenn gleich) steht, um zum Done-Label zu springen, wenn der Wert in Register r2 113 ist Ist dies nicht der Fall, geben wir unsere zweite Zeile aus und springen dann mit der b-Anweisung zum Anfang der Schleife.

Schließlich, nach dem Done-Label, teilen wir dem OS-Kernel mit, dass wir die Ausführung des Programms beenden möchten, genau wie im ersten Programm. Um dieses Programm auszuführen, müssen Sie es nur gemäß den Anweisungen für das erste Programm zusammenbauen und verknüpfen.

Wir haben also eine ziemlich große Menge an Informationen in der kürzesten Form berücksichtigt, aber es ist besser, wenn Sie das Material selbst studieren und mit dem obigen Code experimentieren. Es gibt keinen besseren Weg, sich mit einer Programmiersprache vertraut zu machen, als mit dem Ändern des Codes eines anderen zu experimentieren und die Auswirkungen zu sehen. Sie können jetzt einfache ARM-Assemblersprachenprogramme entwickeln, die Benutzereingaben lesen und Daten ausgeben, während Sie weiterhin Schleifen, Vergleiche und Unterroutinen verwenden. Wenn Sie bis heute noch nicht mit Assemblersprache in Berührung gekommen sind, hoffe ich, dass dieser Artikel die Sprache für Sie etwas verständlicher gemacht und dazu beigetragen hat, das weit verbreitete Klischee zu zerstreuen, dass es sich um ein mystisches Handwerk handelt, das nur wenigen talentierten Entwicklern zur Verfügung steht.

Natürlich sind die im Artikel enthaltenen Informationen zur Verwendung der Assemblersprache für die ARM-Architektur nur die Spitze des Eisbergs. Die Verwendung dieser Programmiersprache ist immer mit einer Vielzahl von Nuancen verbunden und wenn Sie möchten, dass wir in einem der folgenden Artikel darüber schreiben, lassen Sie es uns einfach wissen! In der Zwischenzeit empfehlen wir den Besuch von http://tinyurl.com/nsgzq89, einer ausgezeichneten Ressource mit viel Material zum Schreiben von Programmen für Linux-Systeme, die auf Computern mit CPUs mit ARM-Architektur ausgeführt werden. Viel Spaß beim Programmieren!

Bisherige Artikel aus der Reihe „Assembler School“:

Zunaechst ARM ziemlich ungewöhnlicher Assembler (wenn Sie von lernen x86, MCS51 oder AVR). Aber es hat eine ziemlich einfache und logische Organisation, so dass es schnell absorbiert wird.

Es gibt sehr wenig Dokumentation in russischer Sprache für Assembler. Ich kann Ihnen raten, zu 2 Links zu gehen (vielleicht finden Sie mehr und sagen es mir? Ich werde Ihnen dankbar sein.):
Architektur und Befehlssatz von RISC-Prozessoren der ARM-Familie - http://www.gaw.ru/html.cgi/txt/doc/micros/arm/index.htm
ARM-Assembler verstehen, aus der GBA ASM-Serie, von Mike H, trans. Aquila - http://wasm.ru/series.php?sid=21 .

Der letzte Link hat mir sehr geholfen, den Nebel zerstreut =). Die zweite Sache, die gut helfen kann, ist seltsamerweise der C-Compiler IAR Embedded Workbench für ARM(im Folgenden einfach IAR EW-ARM). Fakt ist, dass es seit Urzeiten (wie allerdings alle anständigen Compiler) in der Lage war, C-Code in Assembler-Code zu kompilieren, der wiederum ebenso leicht von IAR-Assembler in Objekt-Code kompiliert werden kann. Daher gibt es nichts Besseres, als die einfachste Funktion in C zu schreiben, in Assembler zu kompilieren, und es wird sofort klar, welcher Assembler-Befehl was macht, wie Argumente übergeben und wie das Ergebnis zurückgegeben wird. Sie schlagen zwei Fliegen mit einer Klappe - lernen Sie Assemblersprache und erhalten Sie gleichzeitig Informationen darüber, wie Sie Assemblercode in ein C-Projekt integrieren.Ich habe die CRC16-Zählfunktion trainiert und als Ergebnis eine vollwertige Version davon bekommen im Assembler.

Hier ist die ursprüngliche C-Funktion (u16 bedeutet unsigned short, u32 bedeutet unsigned int, u8 bedeutet unsigned char):
// crc16.c-Datei
u16 CRC16 (ungültiger* Datenpuffer, u32-Größe)
{
u16 tmpWort, crc16, idx;
u8bitCnt;
#define CRC_POLY 0x1021;

crc16 = 0;
idx=0;
während (Größe!=0)
{
/* xor hohes Byte von crc16 und Eingabebyte hinzufügen */
tmpWord = (crc16>>8) ^ (*(((u8*)databuf)+idx));
/* Ergebnis in das High-Byte von crc16 schreiben */
tmpWort<<= 8;
crc16=tmpWort+(0x00FF&crc16);
für (bitCnt=8;bitCnt!=0;bitCnt--)
{
/* Prüfe das höchstwertige Bit des CRC-Akkumulators */
wenn (crc16 & 0x8000)
{
crc16<<= 1;
crc16 ^=CRC_POLY;
}
anders
crc16<<= 1;
}
idx++;
Größe--;
}
crc16 zurückgeben;
}

Das Generieren von IAR EW ARM-Assemblercode ist sehr einfach. In den Optionen der crc16.c-Datei (zum Projekt hinzugefügt) habe ich das Kontrollkästchen aktiviert Überschreiben Sie geerbte Einstellungen, und setzen Sie dann auf der Registerkarte Liste 3 Kontrollkästchen - Assembler-Datei ausgeben, Quelle einschließen Und Call-Frame-Informationen einschließen(obwohl das letzte Kontrollkästchen wahrscheinlich weggelassen werden kann - es erzeugt eine Menge unnötiger CFI-Richtlinien). Nach dem Kompilieren wurde die Datei project_folder\ewp\at91sam7x256_sram\List\crc16.s erhalten. Diese Datei kann genauso einfach wie eine C-Datei zum Projekt hinzugefügt werden (sie lässt sich problemlos kompilieren).

Als ich dem C-Compiler natürlich eine ungekürzte Version des C-Codes zusteckte, gab er mir ein solches Assembler-Listing, dass ich nichts darin verstand. Aber als ich alle C-Operatoren bis auf einen aus der Funktion warf, wurde es klarer. Dann fügte ich Schritt für Schritt C-Operatoren hinzu, und am Ende passierte Folgendes:

; crc16.s-Datei
NAME crc16
ÖFFENTLICH CRC16

CRC_POLY EQU 0x1021

ABSCHNITT `.text`:CODE:NOROOT(2)
ARM

// u16 CRC16 (void* databuf, u32-Größe)
;R0 - Ergebnis zurückgeben, CRC16
;R1 - Größenparameter
;R2 - Databuf-Parameter (war am Eingang zu R0)
;R3, R12 - temporäre Register

CRC16:
PUSH (R3, R12); zufällig herausgefunden, dass R3 und R13 gespeichert werden sollen
; nicht unbedingt. Aber ich beschloss, für alle Fälle zu sparen
; Ereignis.
MOVS R2,R0 ;jetzt R2==Datenpuffer
MOV R3,#+0
MOVS R0,R3 ;crc16 = 0
CRC16_LOOP:
CMP R1, #+0 ;alle Bytes verarbeitet (Größe==0)?
BEQ CRC16_RETURN ; wenn ja, beenden
LSR R3, R0, #+8 ;R3 = crc16>>8
LDRB R12, ;R12 = *Datenpuffer
EOR R3, R3, R12 ;R3 = *databuf ^ HIGH (crc16)
LSL R3, R3, #+8 ;R3<<= 8 (tmpWord <<= 8)
UND R0, R0, #+255 ;crc16 &= 0x00FF
R0, R0, R3 HINZUFÜGEN ; crc16 = tmpWort + (0x00FF & crc16)
MOV R12, #+8 ;bitCnt = 8
CRC16_BIT_LOOP:
BEQ CRC16_NEXT_BYTE ;bitCnt == 0?
TST R0,#0x8000 ;Noch nicht alle Bits verarbeitet.
BEQ CRC16_BIT15ZERO ;Überprüfe das höchstwertige Bit von crc16.
LSL R0,R0,#+1 ;crc16<<= 1
MOV R3, #+(LOW(CRC_POLY)) ;crc16 ^= CRC_POLY
ORR R3,R3,#+(HIGH(CRC_POLY)<< 8) ;
EOR R0,R3,R0 ;
B CRC16_NEXT_BIT

CRC16_BIT15ZERO:
LSL R0,R0,#+1 ;crc16<<= 1
CRC16_NEXT_BIT:
SUBS R12,R12,#+1 ;bitCnt--
B CRC16_BIT_LOOP ;

CRC16_NEXT_BYTE:
ADD R2,R2,#+1 ;databuf++
SUBS R1,R1,#+1 ;Größe--
B CRC16_LOOP ;Schleife über alle Bytes

CRC16_RETURN:
POP (R3, R12) ; Register wiederherstellen
BX LR ;Unterprogrammausgang, R0==crc16

Der C-Compiler von IAR produziert überraschend guten Code. Ich konnte es nicht sehr gut optimieren. Hat nur ein zusätzliches temporäres Register weggeworfen, das der Compiler verwenden wollte (aus irgendeinem Grund nahm er LR als zusätzliches temporäres Register, obwohl R3 und R12 ausreichten), und entfernte auch ein paar zusätzliche Befehle, die Zähler prüfen und Flags setzen (einfach Hinzufügen des Suffixes zu den erforderlichen Befehlen).

Dieser Abschnitt beschreibt die Befehlssätze des ARM7TDMI-Prozessors.

4.1 Kurze Beschreibung des Formats

Dieser Abschnitt enthält eine kurze Beschreibung der ARM- und Thumb-Befehlssätze.

Der Schlüssel zu den Befehlssatztabellen ist in Tabelle 1.1 dargestellt.

Der ARM7TDMI-Prozessor basiert auf der ARMv4T-Architektur. Eine vollständigere Beschreibung beider Befehlssätze finden Sie im „ARM Architecture Reference Manual“.

Tabelle 1.1. Schlüssel zu Tabellen

Die ARM-Befehlssatzformate sind in Abbildung 1.5 dargestellt.

Weitere Informationen zu ARM-Befehlssatzformaten finden Sie im „ARM Architectural Reference Manual“.

Abbildung 1.5. ARM-Befehlssatzformate

Einige Befehlscodes sind nicht definiert, verursachen jedoch keine undefinierte Befehlssuche, z. B. ein Multiplikationsbefehl, bei dem Bit 6 auf 1 geändert wurde. Ihre Wirkung kann sich in Zukunft ändern. Das Ergebnis der Ausführung dieser Befehlscodes als Teil des ARM7TDMI-Prozessors ist unvorhersehbar.

4.2 Kurzbeschreibung der ARM-Befehle

Der ARM-Befehlssatz ist in Tabelle 1.2 dargestellt.

Tabelle 1.2. Kurze Einführung in die ARM-Befehle

Operationen Assembly-Syntax
Weiterleitung Weiterleitung MOV(cond)(S)Rd,
Weiterleiten NICHT MVN(cond)(S)Rd,
Übertragen Sie SPSR zur Registrierung MRS (cond) Rd, SPSR
Übertragung von CPSR zur Registrierung MRS (cond) Rd, CPSR
SPSR-Registerübertragung MSR(cond) SPSR(field), Rm
CPSR-Weiterleitung MSR(cond) CPSR(Feld), Rm
Weiterleitung einer Konstante an SPSR-Flags MSR (cond) SPSR_f, #32bit_Imm
Weiterleitung einer Konstante an CPSR-Flags MSR (cond) CPSR_f, #32bit_Imm
Arithmetik Zusatz ADD (cond)(S) Rd, Rn,
Zusatz mit Übertrag ADC (cond)(S) Rd, Rn,
Subtraktion SUB (cond)(S) Rd, Rn,
Subtraktion mit Übertrag SBC (cond)(S) Rd, Rn,
Subtraktion umgekehrte Subtraktion RSB (cond)(S) Rd, Rn,
Subtraktion inverse Subtraktion mit Übertrag RSC (cond)(S) Rd, Rn,
Multiplikation MUL (cond)(S) Rd, Rm, Rs
multiplizieren-akkumulieren MLA (cond)(S) Rd, Rm, Rs, Rn
Multiplizieren Sie lange vorzeichenlose Zahlen UMULL
Multiplikation - vorzeichenlose Akkumulation von langen Werten UMLAL (cond)(S) RdLo, RdHi, Rm, Rs
Signierte Longs multiplizieren SMULL (cond)(S) RdLo, RdHi, Rm, Rs
Multiplikation - vorzeichenbehaftete Akkumulation von langen Werten SMLAL (cond)(S) RdLo, RdHi, Rm, Rs
Vergleich CMP (cond)Rd,
Vergleich ist negativ CMN(cond)Rd,
Rätsel Untersuchung TST (cond)Rn,
Gleichwertigkeitsprüfung TEQ(cond)Rn,
Protokoll. UND UND (cond)(S) Rd, Rn,
Exkl. ODER EOR (cond)(S) Rd, Rn,
ORR ORR (cond)(S) Rd, Rn,
Bit zurücksetzen BIC (cond)(S) Rd, Rn, >
Übergang Übergang (cond) Etikett
Einem Link folgen (cond) Etikett
Springen und Ändern des Befehlssatzes (Lt.)Rn
Lektüre Wörter LDR (cond)Rd,
LDR (cond)T Rd,
Byte LDR (cond) B Rd,
LDR (cond) BT Rd,
signiertes Byte LDR (cond)SB Rd,
halbe Worte LDR (cond)H Rd,
Halbwörter mit Vorzeichen LDR (cond)SH Rd,
Operationen auf mehreren Datenblöcken -
  • mit Vorstufen
  • LDM (cond)IB Rd(, !} {^}
  • gefolgt von Inkrement
  • LDM (cond)IA Rd(, !} {^}
  • mit vorläufigem Dekrement
  • LDM (cond)DB Rd(, !} {^}
  • gefolgt von einem Dekrement
  • LDM (cond)DA Rd(, !} {^}
  • Stapelbetrieb
  • LDM (Ltd.) Rd(, !}
  • Stapelbetrieb und CPSR-Wiederherstellung
  • LDM (Ltd.) Rd(, !} ^
    Stack-Operation mit Benutzerregistern LDM (Ltd.) Rd(, !} ^
    Aufzeichnung Wörter STR (cond) Rd,
    Wörter mit Benutzermoduspräferenz STR (cond)T Rd,
    Byte STR (cond) B Rd,
    Bytes mit Benutzermoduspräferenz STR (cond) BT Rd,
    halbe Worte STR (cond)H Rd,
    Operationen auf mehreren Datenblöcken -
  • mit Vorstufen
  • STM (cond)IB Rd(, !} {^}
  • gefolgt von Inkrement
  • STM (cond)IA Rd(, !} {^}
  • mit vorläufigem Dekrement
  • STM (cond)DB Rd(, !} {^}
    o gefolgt von einem Dekrement STM (cond)DA Rd(, !} {^}
  • Stapelbetrieb
  • STM (Dir.) Rd(, !}
  • Stack-Operation mit Benutzerregistern
  • STM (Dir.) Rd(, !} ^
    Austausch Wörter SWP (cond) Rd, Rm,
    Byte SWP (cond) B Rd, Rm,
    Koprozessor Operation auf Daten CDP(Dir.)S , , CRd, CRn, CRm,
    Transfer zum ARM-Register vom Coprozessor MRC(cond)p , , Rd, CRn, CRm,
    Transfer zum Coprozessor vom ARM-Register MCR(cond)p , , Rd, CRn, CRm,
    Lektüre LDC(cond)p , CRd,
    Aufzeichnung STC(cond)p , CRd,
    Software-Unterbrechung SWI 24bit_Imm

    Sie können sich ausführlich mit dem Befehlssystem im ARM-Modus vertraut machen.

    Adressierungsmodi

    Adressierungsmodi sind Prozeduren, die von verschiedenen Anweisungen verwendet werden, um die von den Anweisungen verwendeten Werte zu generieren. Der ARM7TDMI-Prozessor unterstützt 5 Adressierungsmodi:

    • Modus 1 - Verschiebeoperanden für Datenverarbeitungsbefehle.
    • Modus 2 - Lesen und schreiben Sie ein Wort oder ein Byte ohne Vorzeichen.
    • Modus 3 - Halbwort lesen und schreiben oder Vorzeichenbyte laden.
    • Modus 4 - Mehrfaches Lesen und Schreiben.
    • Modus 5 - Koprozessor lesen und schreiben.

    Adressierungsmodi mit Angabe ihres Typs und Mnemonikcodes sind in Tabelle 1.3 dargestellt.

    Tabelle 1.3. Adressierungsmodi

    Adressierungsmodus Art oder Modus der Adressierung Mnemonischer Code oder Stapeltyp
    Modus 2 Offset-Konstante
    Offset-Register
    Offset-Skalenregister
    Vorindizierter Offset -
    Konstante !
    Registrieren !
    Skalenregister !
    !
    !
    !
    !
    -
    Konstante , #+/-12bit_Offset
    Registrieren , +/-Rm
    Skalenregister
    Modus 2, privilegiert Offset-Konstante
    Offset-Register
    Offset-Skalenregister
    Offset gefolgt von Indizierung -
    Konstante , #+/-12bit_Offset
    Registrieren , +/-Rm
    Skalenregister , +/-Rm, LSL #5bit_shift_imm
    , +/-Rm, LSR #5bit_shift_imm
    , +/-Rm, ASR #5bit_shift_imm
    , +/-Rm, ROR #5bit_shift_imm
    Modus 3 > Offset-Konstante
    !
    Nachträgliche Indizierung , #+/-8bit_Offset
    Registrieren
    Vorindizierung !
    Nachträgliche Indizierung , +/-Rm
    Modus 4, Lesen IA, nachfolgendes Inkrement FD, vollständig absteigend
    ED, leer absteigend
    DA, anschließendes Dekrement FA, voll aufsteigend
    DB-Vorverringerung EA, leer aufsteigend
    Modus 4, Aufnahme IA, nachfolgendes Inkrement FD, vollständig absteigend
    IB, Vorinkrement ED, leer absteigend
    DA, anschließendes Dekrement FA, voll aufsteigend
    DB-Vorverringerung EA, leer aufsteigend
    Modus 5, Coprozessor-Datenübertragung Offset-Konstante
    Vorindizierung !
    Nachträgliche Indizierung , #+/-(8bit_Offset*4)

    Operand 2

    Ein Operand ist ein Teil einer Anweisung, die sich auf Daten oder ein Peripheriegerät bezieht. Die Operanden 2 sind in Tabelle 1.4 dargestellt.

    Tabelle 1.4. Operand 2

    Die Felder sind in Tabelle 1.5 dargestellt.

    Tabelle 1.5. Felder

    Bedingungsfelder

    Bedingungsfelder sind in Tabelle 1.6 dargestellt.

    Tabelle 1.6. Bedingungsfelder

    Feldtyp Suffix Beschreibung Zustand
    Zustand (Zustand) EQ Gleich Z=1
    NE Nicht gleich Z=0
    CS Vorzeichenlos größer als oder gleich C=1
    CC Unsigniert weniger C=0
    MI Negativ N=1
    PL positiv oder null N=0
    VS Überlauf V=1
    VK Kein Überlauf V=0
    HALLO Unsigniert mehr C=1, Z=0
    LS Kleiner als oder gleich ohne Vorzeichen C=0, Z=1
    GE Mehr oder gleich N=V (N=V=1 oder N=V=0)
    LT Weniger NV (N=1 und V=0) oder (N=0 und V=1)
    GT Mehr Z=0, N=V (N=V=1 oder N=V=0)
    LE Gleich oder kleiner als Z=0 oder NV (N=1 und V=0) oder (N=0 und V=1)
    AL Immer wahr Flags werden ignoriert

    4.3 Kurzbeschreibung des Thumb-Befehlssatzes

    Die Formate des Thumb-Befehlssatzes sind in Abbildung 1.6 dargestellt. Weitere Informationen zu ARM-Befehlssatzformaten finden Sie im ARM-Architekturreferenzhandbuch.


    Abbildung 1.6. Thumb-Befehlssatzformate

    Der Thumb-Befehlssatz ist in Tabelle 1.7 dargestellt.

    Tabelle 1.7. Kurze Beschreibung des Thumb-Befehlssatzes

    Operation Assembly-Syntax
    Weiterleiten (Kopieren) Konstanten MOV Rd, #8bit_Imm
    Senior bis Junior MOV Rd, Hs
    Junior bis Senior MOV Hd, Rs
    Senior zu Senior MOV Hd, Hs
    Arithmetik Zusatz Rd, Rs, #3bit_Imm HINZUFÜGEN
    füge Moll zu Moll hinzu Rd, Rs, Rn HINZUFÜGEN
    ältere zu jüngeren addieren HINZUFÜGEN Rd, Hs
    jünger zu älter addieren HD HINZUFÜGEN, Rs
    Älter zu Älter addieren HD, Hs HINZUFÜGEN
    Addition mit einer Konstante Rd HINZUFÜGEN, #8bit_Imm
    SP aufwerten SP HINZUFÜGEN, #7bit_Imm SP HINZUFÜGEN, #-7bit_Imm
    Zusatz mit Übertrag ADC Rd, Rs
    Subtraktion SUB Rd, Rs, Rn SUB Rd, Rs, #3bit_Imm
    ständige Subtraktion SUB Rd, #8bit_Imm
    mit Übertrag subtrahieren SBC Rd, Rs
    Vorzeichenumkehrung NEG Rd, Rs
    Multiplikation MUL Rd, Rs
    Junior mit Junior vergleichen CMP Rd, Rs
    Junior und Senior vergleichen CMP Rd, Hs
    Älter und jünger vergleichen CMP HD, Rs
    Vergleichen Sie älter und älter CMP Hd, Hs
    negativ vergleichen CMN Rd, Rs
    mit konstant vergleichen CMP Rd, #8bit_Imm
    Rätsel UND UND Rd, Rs
    Exkl. ODER EOR Rd, Rs
    ODER ORR Rd, Rs
    Bit zurücksetzen BIC Rd, Rs
    Weiterleiten NICHT MVN Rd, Rs
    Bit-Tests TST Rd, Rs
    Verschieben/Drehen Logische Verschiebung nach links LSL Rd, Rs, #5bit_shift_imm LSL Rd, Rs
    Logische Rechtsverschiebung LSR Rd, Rs, #5bit_shift_imm LSR Rd, Rs
    Arithmetische Rechtsverschiebung ASR Rd, Rs, #5bit_shift_imm ASR Rd, Rs
    Rechtsdrehung ROR Rd, Rs
    Übergang bedingte Sprünge -
    BEQ-Label
    BNE-Kennzeichen
    BCS-Etikett
    BCC-Etikett
    BMI-Etikett
    BPL-Etikett
    BVS-Etikett
    BVC-Etikett
  • C=1, Z=0
  • BHI-Label
  • C=0, Z=1
  • BLS-Etikett
  • N=1, V=1 oder N=0, V=0
  • BGE-Etikett
  • N=1, V=0 oder N=0, V=1
  • BLT-Etikett
  • Z=0 und ((N oder V=1) oder (N oder V=0))
  • BGT-Etikett
  • Z=1 oder ((N=1 oder V=0) oder (N=0 und V=1))
  • BLE-Label
    Bedingungsloser Sprung B-Etikett
    Langer Linklink BL-Etikett
    Optionale Zustandsänderung -
  • an der Adresse in ml. registrieren
  • BX Rs
  • unter der adresse in st. registrieren
  • BX Hs
    Lektüre mit Offset-Konstante -
  • Wörter
  • LDR-Straße,
  • halbe Worte
  • LDRH Rd,
  • Byte
  • LDRB-Straße,
    mit Offsetregister -
  • Wörter
  • LDR-Straße,
  • halbe Worte
  • LDRH Rd,
  • Halbwort unterschreiben
  • LDRSH-Straße,
    LDRB-Straße,
  • signiertes Byte
  • LDRSB-Straße,
    relativ zum PC-Programmzähler LDR-Straße,
    relativ zum Stapelzeiger SP LDR-Straße,
    Die Adresse -
  • über PC
  • Rd, PC, #10bit_Offset HINZUFÜGEN
  • mit SP
  • Rd, SP, #10bit_Offset HINZUFÜGEN
    Mehrfaches Lesen LDMIA Rb!,
    Aufzeichnung mit Offset-Konstante -
  • Wörter
  • STR-Straße,
  • halbe Worte
  • Strh Straße,
  • Byte
  • Straße,
    mit Offsetregister -
  • Wörter
  • STR-Straße,
  • halbe Worte
  • Strh Straße,
  • Byte
  • Straße,
    bezüglich SP STR-Straße,
    Mehrfacher Eintrag STMIA Rb!,
    Pushing/Popping vom Stack Push-Register auf den Stack DRÜCKEN
    Schieben Sie LR und Register auf den Stack DRÜCKEN
    Pop-Register aus dem Stack POP
    Pop-Register und PC vom Stack POP
    Software-Unterbrechung - SWI 8bit_Imm

    Gegenwärtig werden zur Programmierung auch recht einfacher Mikrocontroller in der Regel Hochsprachen verwendet, die Teilmengen der Sprache C oder C++ sind.

    Beim Studium der Architektur von Prozessoren und ihrer Merkmale ist es jedoch ratsam, Assemblersprachen zu verwenden, da nur ein solcher Ansatz die Identifizierung von Merkmalen der untersuchten Architektur gewährleisten kann. Aus diesem Grund erfolgt die weitere Darstellung in der Assemblersprache.

    Bevor Sie mit der Betrachtung von ARM7-Befehlen fortfahren, müssen Sie die folgenden Merkmale beachten:

      Unterstützung für zwei Befehlssätze: ARM mit 32-Bit-Befehlen und THUMB mit 16-Bit-Befehlen. Das Folgende ist ein 32-Bit-Befehlssatz, das Wort ARM bedeutet Anweisungen, die zu diesem Format gehören, und das Wort ARM7 - die CPU selbst.

      Unterstützung für zwei 32-Bit-Adressformate: Little-Endian-Prozessor und Little-Endian-Prozessor. Im ersten Fall befindet sich das höchstwertige Bit (Most Significant Bit - MSB) im niederwertigsten Bit des Wortes, im zweiten Fall im höchstwertigen. Dies bietet Kompatibilität mit anderen Familien von 32-Bit-Prozessoren, wenn Hochsprachen verwendet werden. Bei einigen Prozessorfamilien mit ARM-Kern wird jedoch nur Little Endian verwendet (dh MSB ist das höchstwertige Bit der Adresse), was die Arbeit mit dem Prozessor erheblich vereinfacht. Da der für ARM7 verwendete Compiler mit Code in beiden Formaten arbeitet, muss darauf geachtet werden, dass das Wortformat richtig eingestellt ist, da sonst der resultierende Code „umgekrempelt“ wird.

      Möglichkeit, verschiedene Arten von Verschiebungen an einem der Operanden "auf dem Pass" durchzuführen, bevor er in der ALU verwendet wird

      Unterstützung für die bedingte Ausführung eines beliebigen Befehls

      Möglichkeit, das Ändern der Flags der Ergebnisse der Operation zu verbieten.

        1. Bedingte Befehlsausführung

    Eines der wichtigen Merkmale des ARM-Befehlssatzes ist, dass er die bedingte Ausführung beliebiger Befehle unterstützt. In herkömmlichen Mikrocontrollern sind die einzigen bedingten Befehle bedingte Sprünge und vielleicht eine Reihe anderer, wie Befehle zum Prüfen oder Ändern des Zustands einzelner Bits. Im ARM-Befehlssatz werden die oberen 4 Bits des Befehlscodes immer mit den Zustandsflags im CPSR-Register verglichen. Wenn ihre Werte nicht übereinstimmen, wird der Befehl in der Entschlüsselungsphase durch den Befehl NOP (keine Operation) ersetzt.

    Dadurch wird die Ausführungszeit von Programmteilen mit "kurzen" Übergängen erheblich reduziert. Wenn Sie beispielsweise quadratische Gleichungen mit reellen Koeffizienten und beliebigen Wurzeln mit negativer Diskriminante lösen, müssen Sie das Vorzeichen der Diskriminante ändern, bevor Sie die Quadratwurzel berechnen, und das Ergebnis dem Imaginärteil der Antwort zuweisen.

    Bei der herkömmlichen Lösung dieses Problems ist es notwendig, einen bedingten Sprungbefehl einzugeben. Die Ausführung dieses Befehls dauert mindestens 2 Zyklen – Entschlüsselung und Laden des neuen Adresswerts in den Programmzähler und eine zusätzliche Anzahl von Zyklen zum Laden der Befehlspipeline. Bei der bedingten Befehlsausführung mit positiver Diskriminante wird der Vorzeichenwechselbefehl durch eine Leeroperation ersetzt. In diesem Fall wird die Befehlspipeline nicht gelöscht und die Verluste betragen nicht mehr als einen Taktzyklus. Die Schwelle, bei der das Ersetzen bedingter Befehle durch den NOP-Befehl effizienter ist als die Ausführung herkömmlicher bedingter Verzweigungsbefehle und das damit verbundene Wiederauffüllen der Pipeline, ist gleich ihrer Tiefe, d. h. drei.

    Um diese Funktion zu implementieren, müssen Sie der grundlegenden mnemonischen Notation von Assembler-Befehlen (und auch C) eines der sechzehn Präfixe hinzufügen, die die Testzustände der Bedingungs-Flags definieren. Diese Präfixe sind in der Tabelle angegeben. 3. Dementsprechend gibt es für jedes Team 16 Optionen. Beispielsweise der folgende Befehl:

    MOVEQ R1, #0x008

    bedeutet, dass die Zahl 0x00800000 nur dann in das R1-Register geladen wird, wenn das Ergebnis des letzten Datenverarbeitungsbefehls „gleich“ war oder ein 0-Ergebnis erhalten wurde und das Flag (Z) des CPSR-Registers entsprechend gesetzt ist.

    Tisch 3

    Befehlspräfixe

    Bedeutung

    Z installiert

    Z fiel

    Mit installiert

    Größer als oder gleich (ohne Vorzeichen)

    C zurückgesetzt

    Unten (unsigniert)

    N installiert

    Negatives Ergebnis

    N ist gefallen

    Positives Ergebnis oder 0

    V installiert

    Überlauf

    V ist gefallen

    Kein Überlauf

    Mit installiert,

    Z fiel

    Oben (unsigniert)

    Mit fallen gelassen,

    Z installiert

    Kleiner oder gleich (ohne Vorzeichen)

    Größer als oder gleich (vorzeichenbehaftet)

    N ist nicht gleich V

    Kleiner als (signiert)

    Z zurücksetzen UND

    (N gleich V)

    Mehr (signiert)

    Z wird ODER gesetzt

    (N ist ungleich V)

    Kleiner als oder gleich (mit Vorzeichen)

    (ignoriert)

    Bedingungslose Ausführung

    Fortsetzung des Themas:
    Linux

    Obwohl das soziale Netzwerk Odnoklassniki in Russland derzeit nicht der absolute Spitzenreiter in Bezug auf die Reichweite des Publikums ist, enthält es immer noch eine große Anzahl von...