|
|
| (96 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) |
| Zeile 1: |
Zeile 1: |
| Der ARM ist ein sogenannter RISC-Computer, was das Erlernen von Assembler theoretisch einfacher macht.
| | <!-- /satyria.de/source/assem/ARM64Assembler --> |
|
| |
|
| * Wir verwenden Linux
| | == Grundlagen == |
| * Grundzustand von Rasbpian ist ausreichend
| | Der ARM-Prozessor ist ein sogenannter RISC-Computer (Reduced Instruction Set Computer). Das Designprinzip von RISC-Prozessoren führt zu einem kleineren und übersichtlicheren Befehlssatz, was das Erlernen von Assembler theoretisch einfacher macht. In dieser Einführung werden wir die grundlegenden Konzepte der ARM64-Bit Assembler Programmierung verständlich und praxisorientiert erklären, sodass sie auch für Anfänger zugänglich ist. |
|
| |
|
| * Zahlen
| | == Was ist Assembler? == |
| -> Dezimal, Binär, Hexadezimal
| | Assembler ist eine Programmiersprache, die es ermöglicht, den Computer auf sehr niedriger Ebene direkt zu steuern. Anders als Hochsprachen wie C oder Python kommuniziert Assembler direkt mit der Hardware, was eine präzise Kontrolle über den Prozessor und den Speicher ermöglicht. Diese direkte Steuerungsmöglichkeit macht Assembler zu einer mächtigen Sprache für ressourcenintensive oder sehr spezifische Aufgaben. |
| * CPU-Register
| |
| -> Ein 64-Bit-Programm auf einem ARM-Prozessor im Benutzermodus hat Zugriff auf 31 Allzweckregister, einen Programmzähler (PC) und eine Kombination aus Nullregister/Stapelzeiger
| |
| * x0-x30
| |
| * SP, XZR
| |
| * x30, LR
| |
| * PC
| |
| * w0-w30, wzr: sind x-Register, die die unteren 32-Bit verwenden.
| |
| -> Zusätzliche Register Gleitkommaoperationen, Neon-Coprozessor, später
| |
|
| |
|
| * Aufbau von Data processing instructions:
| | == Aufbau dieses Tutorials == |
| | 31 | 30 | 29 | 28-24 | 23-22 | 21 | 20-16 | 15-10 | 9-5 | 4-0 |
| | Dieses Tutorial ist so strukturiert, dass es sich von einem Kapitel zum nächsten Schritt für Schritt selbst erklärt. Zur Demonstration der Beispiele verwende ich einen Raspberry Pi 5, aber die Beispiele sollten ebenso auf den Modellen Raspberry Pi 3 und Raspberry Pi 4 funktionieren, sofern diese mit einem 64-Bit-Linux-System betrieben werden. Zu Beginn werden wir uns ansehen, wie man überhaupt Assemblerprogramme schreibt und diese auf einem ARM64-System ausführt. |
| | Bits | Opcode | Set Condition Code | Opcode | Shift | 0 | Rm | imm6 | Rn | Rd |
| |
| -> Erklärung | |
|
| |
|
| * Memory | | == Inhalt == |
| -> Instruktionen 32-Bit, Register 64-Bit, Memory-Adresssierung 64-Bit -> wie lösen.
| | * [[Programmierumgebung unter Linux erstellen und testen]] |
| | | ** [[Programmierumgebung unter Linux erstellen und testen#Einrichtung der Entwicklungsumgebung|Einrichtung der Entwicklungsumgebung]] |
| * Der GCC-Assembler | | ** [[Programmierumgebung unter Linux erstellen und testen#Installation der notwendigen Tools wie Assembler, Compiler und Debugger|Installation der notwendigen Tools wie Assembler, Compiler und Debugger]] |
| -> Maschinencode in lesbare Form
| | ** [[Programmierumgebung unter Linux erstellen und testen#Erste Schritte mit einem einfachen "Hello, World!"-Programm in Assembler|Erste Schritte mit einem einfachen "Hello, World!"-Programm in Assembler]] |
| -> Aufbau von Befehlen, Beispiel ldr, mov
| | ** [[Programmierumgebung unter Linux erstellen und testen#Testen der Umgebung durch Kompilieren und Ausführen von Assemblerprogrammen|Testen der Umgebung durch Kompilieren und Ausführen von Assemblerprogrammen]] |
| | | * [[Tools, die zur Programmierung benötigt werden]] |
| * Erstes Programm: Hello World | | ** [[Tools, die zur Programmierung benötigt werden#Assembler (as)|Allgemeine Einführung in gängige Tools: Assembler (as), Linker (ld), Compiler (gcc) und make]] |
| .global _start
| | ** [[Tools, die zur Programmierung benötigt werden#Debugging-Tool (gdb)|Verwendung von Debugging-Tools wie gdb]] |
| _start:
| | ** [[Tools, die zur Programmierung benötigt werden#IDEs und Texteditoren, die Assembler unterstützen|IDEs und Texteditoren, die Assembler unterstützen]] |
| mov x0,#1
| | * [[Allgemeines zu Zahlen (Dezimal, Binär, Hexadezimal)]] |
| ldr x1,=helloworld
| | * [[Das erste Programm "Hello World"]] |
| mov x2,#13
| | * [[Register und Speicher]] |
| mov x8,#64
| | * [[Laden und Speichern von Werten]] |
| svc 0
| | ** [[Laden und Speichern von Werten#Laden von konstanten Werten in Register|Laden von konstanten und variablen Werten in Register]] |
| mov x0,#0
| | ** [[Laden und Speichern von Werten#Speichern von Registerwerten im Speicher|Speichern von Registerwerten im Speicher]] |
| mov x8,#93
| | ** [[Laden und Speichern von Werten#Laden und Speichern: Die Befehle ldr und str|Verwendung der Befehle ldr und str]] |
| svc 0
| | * [[Addieren und Subtrahieren]] |
| .data
| | ** [[Addieren und Subtrahieren#Einfache arithmetische Operationen: add und sub|Einfache arithmetische Operationen: add, sub]] |
| helloworld:
| | ** [[Addieren und Subtrahieren#Umgang mit Überlauf und Unterlauf|Umgang mit Überlauf und Unterlauf]] |
| .ascii "Hello World!\n"
| | ** [[Addieren und Subtrahieren#Arbeiten mit mehreren Registern und direkten Werten|Arbeiten mit mehreren Registern und direkten Werten]] |
| helloworldlen:
| | ** [[Addieren und Subtrahieren#Umgang mit Überlauf und Unterlauf bei 128-Bit-Arithmetik in ARM64-Assembler|Umgang mit Überlauf und Unterlauf bei 128-Bit-Arithmetik in ARM64-Assembler]] |
| .word .-helloworld
| | * [[Multiplizieren, Dividieren und Akkumulation]] |
| | | ** [[Multiplizieren, Dividieren und Akkumulation|Multiplizieren und Dividieren von Werten: mul, div]] |
| | | ** [[Multiplizieren, Dividieren und Akkumulation#Akkumulation|Erweitere Operationen wie akkumuliertes Multiplizieren: mla, mls]] |
| as -o HelloWorld.o HelloWorld.s | | * [[Shiften und Rotation]] |
| ld -o HelloWorld HelloWorld.o | | * [[Logische Operatoren]] |
| | | * [[Sprungadressen (Labels)]] |
| * Erklärung Code | | * [[Programmablauf steuern]] |
| -> Kommentare | | ** [[Programmablauf steuern|Bedingte und unbedingte Sprungbefehle: b, bl, cbz, cbnz]] |
| -> globales Symbol _start | | ** [[Programmablauf steuern#Schleifen|Schleifen und Verzweigungen]] |
| -> Assembler-Befehle:
| | ** [[Programmablauf steuern#Bedingter Sprung|Vergleichsoperationen und bedingte Ausführung]] |
| --> mov, ldr, svc 0
| | * [[Funktionen und Stack]] |
| -> data
| | ** [[Funktionen und Stack#Funktion aufrufen und Rückkehr|Funktion aufrufen und Rückkehr]] |
| | | ** [[Funktionen und Stack#Funktionsparameter und Rückgabewerte|Funktionsparameter und Rückgabewerte]] |
| * Linux-Systemaufrufe | | ** [[Funktionen und Stack#Verwendung des Stacks für lokale Variablen und Funktionsaufrufe|Verwendung des Stacks für lokale Variablen und Funktionsaufrufe]] |
| -> Parameter in den Registern X0–X7
| | ** [[Funktionen und Stack#Regeln für das Aufrufen von Funktionen (AAPCS)|Regeln für das Aufrufen von Funktionen (AAPCS)]] |
| -> Rückgabe x0
| | * [[Systemaufrufe]] |
| -> Funktionsnummer in X8
| | ** [[Systemaufrufe#Einführung in Linux-Systemaufrufe|Einführung in Linux-Systemaufrufe]] |
| | | ** [[Systemaufrufe#Grundlegende Systemaufrufe|Ausführung von grundlegenden Systemaufrufen wie exit, read, write, open, close]] |
| * Diassemblieren | | ** [[Systemaufrufe#Übergabe von Parametern und Entgegennahme von Rückgabewerten|Übergabe von Parametern und Entgegennahme von Rückgabewerten]] |
| -> objdump -s -d HellowWorld.o
| | ** [[Systemaufrufe#Verweis auf alle verfügbaren Linux-Systemaufrufe|Übersicht der Linux ARM64 Systemaufrufen]] |
| | | <!--* [[GPIO Programmierung]] |
| == Laden und Addieren ==
| | ** [[GPIO Programmierung#Grundlagen der GPIO-Programmierung|Grundlagen der GPIO-Programmierung]] |
| * mov add | | ** [[GPIO Programmierung#Direkte Steuerung der GPIO-Pins|Direkte Steuerung der GPIO-Pins]] |
| * negative Zahlen | | ** [[GPIO Programmierung#Besondere Register und Konfiguration|Besondere Register und Konfiguration]]--> |
| -> Beispiel: 5 + -3
| | * [[Interaktion mit C]] |
| 3 in 1 byte is 0x03 or 0000 0011.
| | ** [[Interaktion mit C#C-Funktionen aus Assembler aufrufen|C-Funktionen aus Assembler aufrufen]] |
| Inverting the bits is
| | ** [[Interaktion mit C#Assemblerfunktionen in C aufrufen|Assemblerfunktion in C aufrufen]] |
| 1111 1100
| | ** [[Interaktion mit C#Inline-Assembler in C|Inline-Assembler in C]] |
| Add 1 to get
| | ** [[Interaktion mit C#Bibliotheken verwenden|Bibliotheken verwenden]] |
| 1111 1101 = 0xFD
| | * [[Gleitkommaoperationen]] |
| Now add
| | ** [[Gleitkommaoperationen#FPU-Register: Überblick|FPU-Register]] |
| 5 + 0xFD = 0x102 = 2
| | *** [[Gleitkommaoperationen#Registerüberblick|Überblick über FPU (Floating Point Unit) und ihre Register]] |
| * Big vs. Little Endian | | ** [[Gleitkommaoperationen#Verwendung der FPU-Register in Funktionen|Verwendung der Register in Funktionen]] |
| -> Reihenfolge der Bytes im Speicher
| | ** [[Gleitkommaoperationen#Hinweise zur Verwendung von FPU-Registern|Arbeiten mit FPU-Registern]] |
| -> ARM-Prozessor erlaubt beide Versionen
| | *** [[Gleitkommaoperationen#Beispiele für die Verwendung|Ausführung von Gleitkommaoperationen: fadd, fsub, fmul, fdiv]] |
| -> Linux verwendet Little Endian
| | ** [[Gleitkommaoperationen#Konvertierung von Gleitkommazahlen|Konvertierung von Gleitkommazahlen]] |
| * Shiften und Rotation | | *** [[Gleitkommaoperationen#Konvertierung von Gleitkommazahlen|Umwandlung zwischen Ganzzahlen und Gleitkommazahlen]] |
| * Carry-Flag
| | ** [[Gleitkommaoperationen#Vergleich von Gleitkommazahlen und bedingte Sprungbefehle|Vergleichen]] |
| • Logical shift left
| | *** [[Gleitkommaoperationen#Vergleich von Gleitkommazahlen und bedingte Sprungbefehle|Vergleich von Gleitkommazahlen und bedingte Sprungbefehle]] |
| • Logical shift right
| | * [[NEON Coprozessor]] |
| • Arithmetic shift right
| | ** [[NEON Coprozessor#Verwendung des NEON-Coprozessors im Raspberry Pi|Verwendung des NEON-Coprozessors im Raspberry Pi]] |
| • Rotate right
| | ** [[NEON Coprozessor#LANE-Prinzip: Überblick|LANE-Prinzip]] |
| * Laden von Registern | | ** [[NEON Coprozessor#Anwendung und Kontext im Raspberry Pi|Anwendung und Kontext im Raspberry Pi]] |
| -> mov x0,x1; Ein Alias. Gleiche ist möglich mit add x0, xzr, x1, aber tatsächlich ist es orr x0,xzr,x1
| | <br> |
| -> Verwendung von Aliase, um den Code besser zu verstehen, Problem beim debuggen, objdump, da eventuell andere Aliases verwendet werden.
| | * Anhang |
| * mov | | ** [[Diassemblieren]] |
| -> 1. MOVK XD, #imm16{, LSL #shift}
| | ** [[Big vs. Little Endian]] |
| 2. MOV XD, #imm16{, LSL #shift}
| | ** [[Aliase]] |
| 3. MOV XD, XS
| | ** [[Direktiven|Assembler Direktiven (as/gcc)]] |
| 4. MOV XD, operand2
| | *** [[Direktiven#4. Daten ausrichten: .align|Daten ausrichten]] |
| 5. MOVN XD, operand2
| | ** [[Makros in Assembler]] |
| * add/adc | | ** [[Übersicht der Linux ARM64 Systemaufrufen]] |
| 1. ADD{S} Xd, Xs, Operand2
| | *** [[Übersicht der Fehlercodes|Übersicht der Fehlercodes von Systemaufrufen]] |
| 2. ADC{S} Xd, Xs, Operand2
| |
| | |
| Beispiel:
| |
| .global _start
| |
| _start: MOVN W0, #2
| |
| ADD W0, W0, #1
| |
| MOV X8, #93 // Service command code 93
| |
| SVC 0
| |
| Rückgabe in w0 -> Kann mit "echo $?" ausgegeben werden.
| |
| | |
| * Add with Carry | |
| -> Übertrag.
| |
| -> Beispiel:
| |
| adds x1,x3,x5 //addiert untere 64-Bit
| |
| adc x0,x2,x4 //addiert obere 64-Bit mit Übertrag von zuvor
| |
| | |
| * SUB/SBC | |
| == Tools ==
| |
| * GNU MAKE | |
| -> make -B: erstellt neue Kompilierungen
| |
| | |
| * GDB | |
| break (b) line Set breakpoint at line
| |
| run (r) Run the program
| |
| step (s) Single step program
| |
| continue (c) Continue running the program
| |
| quit (q or control-d) Exit gdb
| |
| control-c Interrupt the running program
| |
| info registers (i r) Print out the registers
| |
| info break Print out the breakpoints
| |
| delete n Delete breakpoint n
| |
| x /Nuf expression Show contents of memory
| |
| | |
| * Gross-Compiling | |
| * Emulation | |
| | |
| == Programmablauf steuern ==
| |
| * Bedingungsloser Sprung | |
| -> b label | |
| * Bedingungsflags | |
| negativ: N gesetzt, wenn Ergebnis negativ ist
| |
| zero: z gesetzt wenn Ergebnis null ist
| |
| carry: c gesetzt, wenn es einen Überlauf gab. add -> wenn größer als Zahlenbereich (Überlauf), Subtraktion gesetzt, wenn Ergebnis keine Ausleihe. bei Verschieben letzte Bit herausgeschoben.
| |
| overflow: o gesetzt bei addition und subtraktion, wenn Ergebnis größer oder gleich 2^31, oder kleiner -2^31
| |
| | |
| Flags werden im NZCV-Systemregister gespeichert
| |
| | |
| * Verzweigung bei Bedingung | |
| -> b.{condition} | |
| {condition} Flags Meaning
| |
| EQ Z set Equal
| |
| NE Z clear Not equal
| |
| CS or HS C set Higher or same (unsigned >=)
| |
| CC or LO C clear Lower (unsigned <)
| |
| MI N set Negative
| |
| PL N clear Positive or zero
| |
| VS V set Overflow
| |
| VC V clear No overflow
| |
| HI C set and Z clear Higher (unsigned >)
| |
| LS C clear and Z set Lower or same (unsigned <=)
| |
| GE N and V the same Signed >=
| |
| LT N and V differ Signed <
| |
| GT Z clear, N and V the same Signed >
| |
| LE Z set, N and V differ Signed <=
| |
| AL Any Always (same as no suffix)
| |
| | |
| * CMP Xn,Operand2 | |
| -> subtrahiert Operand2 von Xn
| |
| | |
| * Schleifen | |
| ** FOR NEXT | |
| for i = 1 to 10
| |
| Mache etwas
| |
| next i
| |
| | |
| In Assembler:
| |
| mov w2,#1 //i=1
| |
| loop:
| |
| //Mache etwas
| |
| add w2,w2,#1 // i=i+1
| |
| cmp w2,#10 // if i<10
| |
| b.le loop // then goto loop
| |
| ** WHILE | |
| while x < 10
| |
| Mache etwas
| |
| end while
| |
| | |
| In Assembler:
| |
| // w2 zuvor initialisiert -> x
| |
| loop:
| |
| cmp w2,#10
| |
| b.ge loopend
| |
| // Mache etwas
| |
| b loop
| |
| loopend:
| |
| | |
| ** if/then/else | |
| if x < 10 then
| |
| If ist okay
| |
| else
| |
| else kommt zum zuge
| |
| end if
| |
| | |
| In Assembler:
| |
| // wert x in w2
| |
| cmp w2,#10
| |
| b.ge label_else
| |
| //If ist okay
| |
| b label_EndIf
| |
| label_else:
| |
| //else kommt zum zuge
| |
| label_EndIf:
| |
| | |
| ** do/until | |
| do
| |
| Mache etwas
| |
| until a==0
| |
| | |
| In Assembler:
| |
| mov w0,#1
| |
| loop:
| |
| //mache etwas
| |
| cmp w0,#0
| |
| b.ne loop
| |
| | |
| * Logische Operatoren | |
| and{s} Xd, Xs, Operand2
| |
| eor{s} Xd, Xs, Operand2
| |
| orr{s} Xd, Xs, Operand2
| |
| bic{s} Xd, Xs, Operand2
| |
| | |
| | and | eor | orr | bic |
| |
| Xs | 1100 | 1100 | 1100 | 1100 |
| |
| Operand2 | 1010 | 1010 | 1010 | 1010 |
| |
| Ergebnis | 1000 | 0110 | 1110 | 0100 |
| |
| | |
| * CMN Xn, Operand2 | |
| -> addiert Operand2 von Xn
| |
| * TST Xn, Operand2 | |
| -> Bitweise AND von Operand2 und Xn
| |
| | |
| == Speicher ==
| |
| * Speicheradressen 64 Bit lang | |
| * .data | |
| -> Dezimalzahl: Beginnt mit 1-9 und enthält 0-9
| |
| -> Oktalzahl: Beginnt mit 0 und enthält 0-7
| |
| -> Binärzahl: Beginnt mit 0b und enthält 0-1
| |
| -> Hexadezimal: Beginnt mit 0x und enthält 0-f
| |
| -> Gleitkommawerte: Beginnt mit 0f oder 0e und enthält die Gleitkomma-Zahl
| |
| -> Präfix: "-" nimmt das Zweierkomplement, "~" nimmt das Einerkompliment
| |
| .byte -0xa3, -22, ~0b11010010
| |
| | |
| Directive Beschreibung
| |
| .ascii Eine Zeichenfolge in doppelten Anführungszeichen
| |
| .asciz Eine mit 0 Bytes abgeschlossene ASCII-Zeichenfolge
| |
| .byte 1-byte Ganzzahl
| |
| .double Gleitkommawerte mit doppelter Genauigkeit
| |
| .float Gleitkommawerte
| |
| .octa 16-byte Ganzzahl
| |
| .quad 8-byte Ganzzahl
| |
| .short 2-byte Ganzzahl
| |
| .word 4-byte Ganzzahl
| |
| | |
| * Hilfreiche Direktiven:
| |
| .fill Anzahl, Größe, Inhalt: Erzeugt einen Speicher mit dem "Inhalt" der "Größe" und "Anzahl".
| |
| .rept Anzahl ... .endr: Wiederholt "Anzahl" den Inhalt von "..."
| |
| | |
| * ASCII-Strings
| |
| Escape Description
| |
| \b Backspace (ASCII code 8)
| |
| \f Seitenvorschub (ASCII code 12)
| |
| \n Neue Zeile (ASCII code 10)
| |
| \r Return (ASCII code 13)
| |
| \t Tabulator (ASCII code 9)
| |
| \ddd Ein Okctaler ASCII code (ex \123)
| |
| \xdd Ein Hexadezimaler ASCII code (ex \x4F)
| |
| \\ Das “\” Zeichen
| |
| \” Das Anführungszeichen
| |
| | |
| * Daten ausrichten
| |
| .align Ausrichtung: Setzt den nächsten Wert auf ein "Ausrichtung" definierten Speicher.
| |
| Beispiel:
| |
| .byte 0xef
| |
| .align 4 //Ausrichtung auf Wortbreite
| |
| .word 0x26ef43de
| |
| * ldr
| |
| * Relative Adressierung am PC
| |
| -> ldr PC-relativ Adresse 19-Bit breit (ca. +/-1MB)
| |
| Beispiel:
| |
| ldr x1,=hello
| |
| ...
| |
| .data
| |
| hello:
| |
| .ascii "Hallo"
| |
| Die Adresse zum Label "hello" wird relativ zum PC ermittelt.
| |
| -> TRICK:
| |
| ldr x1,=0x1234567890abcdef //Direkte 64-Bit Zuweisung
| |
| Der Assembler wandelt diese Anweisung:
| |
| ldr x1,#8
| |
| .quad 0x1234567890abcdef
| |
| | |
| * ldr{Typ} xt,[xa], wobei für Typ steht:
| |
| B Unsigned byte
| |
| SB Signed byte
| |
| H Unsigned halfword (16 bits)
| |
| SH Signed halfword (16 bits)
| |
| SW Signed word
| |
| | |
| Beispiel:
| |
| ldr x0,=MeineZahl //läd die Adresse meiner Variablen
| |
| ldr x0,[x0] //läd dann den Inhalt der Variablen
| |
| .data
| |
| MeineZahl: .quad 0x1234567fe
| |
| | |
| *str{Typ} xt,[xa]
| |
| | |
| *Indexsierung (Arrays)
| |
| PseudoCode:
| |
| dim a[10] as word
| |
| a[5] = 10 //setze das 5 Element mit 10
| |
| x = a[8] //Schreibe das 8 Element nach x
| |
| for i = 1 to 10
| |
| a[i] = i *8
| |
| next
| |
| | |
| In Assembler:
| |
| ldr x0,=MeinArray //Lade die Adresse von MeinArray nach x0
| |
| mov w1,#10 //Speichere 10 in w1 ab
| |
| str w1,[x0,#(4*4)] //Speichere w1 in das 5 Element (Begin ist 0) (Word = 4 Byte)
| |
| ldr w1,[x0,#(7*4)] //lade das 8 Element nach w1
| |
| mov w2,#1 //i=1
| |
| forloop:
| |
| lsl w4,w2,#3 // i * 8
| |
| str w4,[x0, w2, lsl #2] // speichere der Wert aus w4 nach Basisadresse x0 mit index aus w2, der um 4 Multipliziert wird.
| |
| add w2,w2,#1 // i=i+1
| |
| cmp w2,#10 // if i<10
| |
| b.le forloop
| |
| * Der Index kann auch negativ sein.
| |
| | |
| * Post-indexierte Adressierung
| |
| -> ldr x1,[x2], #4 //Holt den Wert aus x2 und erhöht anschließen x2 um 4
| |
| | |
| * ldp, stp -> 128-Bit
| |
| ldr x1,=GrosseZahl
| |
| ldp x2,x3,[x1]
| |
| stp x2,x3,[x1]
| |
| .data
| |
| GrosseZahl:
| |
| .octa 0x12345678901234567890123456789012
| |
| | |
| == Funktionen und Stack ==
| |
| * Stack: Unter Linux 8MB groß
| |
| Speichern auf den Stack:
| |
| str x0,[sp,#-16]!
| |
| -> Warum 16Bytes? SP muss 16Bytes ausgerichtet sein.
| |
| Laden vom Stack:
| |
| ldr x0,[sp],#16
| |
| Zwei Register gleichzeitig:
| |
| stp x0,x1,[sp,#-16]!
| |
| ldp x0,x1,[sp],#16
| |
| | |
| * bl
| |
| -> Linkregister - x30, Zeiger auf nächsten Befehl
| |
| Zurück mit ret (verwendet x30 als Rücksprungadresse)
| |
| | |
| Beispiel für eine Funktion:
| |
| ...
| |
| bl MeineFunktion
| |
| ...
| |
| ------
| |
| MeineFunktion:
| |
| ...
| |
| ret
| |
| | |
| * Nur ein LR, Problem wenn in der Funktion eine weitere aufgerufen wird.
| |
| -> Lösung, der Stapel
| |
| In der Funktion, LR sichern und vor beendigung der Funktion LR wieder herstellen
| |
| MeineFunktion:
| |
| str lr,[sp,#-16]!
| |
| ...
| |
| ldr lr,[sp],#16
| |
| ret
| |
| | |
| * Parameter übergabe und zurück
| |
| Übergabe in x0-x7, wenn mehr über stack
| |
| Rückgabe in x0. Möglich ist auch eine Ganzzahl bis 128-Bit, dann x0 und x1
| |
| Bei Rückgabe mehrere Daten werden Speicheradressen verwendet, die auch in x0 übergeben wird, in denn die Daten stehen.
| |
| | |
| * Verwaltung der Register
| |
| Regeln, wer verantwortlich ist (Aufzurufende Funktion, oder die aufgerufene Funktion, für die Register
| |
| - x0-x7 Funktionsparameter, kann beliebig geändert werden.
| |
| - x0-x18 Können frei verwendet werden, diese werden nicht gesichert.
| |
| - x19-x30 Müssen gesichert werden bevor diese verwendet werden. Dies erfolgt über Stack
| |
| | |
| * Frame Pointer
| |
| C-Programmierkonvention -> x29
| |
| Verwendung zum Beispiel für Lokale Variablen
| |
| -> Speicherbereich im Stack freigeben, und FP darauf setzen:
| |
| Subtraktion, um dem Platzbedarf für die Variablen zu erstellen (Achtung auf 16 ausgerichtet)
| |
| sub sp,sp,#16
| |
| Damit hat man Platz für 4 32-Bit Integers
| |
| str w0,[sp]
| |
| str w1,[sp,#4]
| |
| str w2,[sp,#8]
| |
| str w3,[sp,#12]
| |
| Bevor die Funktion beendet wird, muss der Stack wieder angepasst werden:
| |
| add sp,sp,#16
| |
| | |
| Soweit so gut. Aber wenn wir nun einige Dinge mit dem Stack durchführen, verlieren wir irgendwann den Überblick. Hier kommt dann unser FramePointer zum Zuge. Wir setzen einfach den FramePointer auf die Adresse des Stacks, an die Position, wo wir die Variablen abgelegt haben:
| |
| sub fp,sp,#16
| |
| sub sp,sp,#16
| |
| oder:
| |
| sub sp,sp,#16
| |
| mov fp,sp
| |
| | |
| Nun kann eindeutig auf die Variablen zugegriffen werden:
| |
| str w0,[fp]
| |
| str w1,[fp,#4]
| |
| str w2,[fp,#8]
| |
| str w3,[fp,#12]
| |
| | |
| Achtung! Das Register x29 (fp) ist ein Register, welches wir bei Funktionsstart auf den Stack sichern müssen und vor dem verlassen der Funktion wieder herstellen müssen.
| |
| | |
| * .equ
| |
| -> Definiert Werte einer lesbaren Schreibweise zu. Allerdings nur mit Zahlen möglich
| |
| | |
| * Makros
| |
| .macro Name übergabe1,übergabe2,...
| |
| Beispiel:
| |
| .macro speicher wert1,wert2
| |
| ldr x0,=\wert1
| |
| ldr x1,=\wert2
| |
| .endm
| |
| | |
| .global _start
| |
| _start:
| |
| speicher buffer1,buffer2
| |
| ...
| |
| .data
| |
| buffer1: .fill 255,1,0
| |
| buffer2: .fill 255,1,0
| |
| | |
| Beim Aufruf "speicher" wird an dieser Stelle des Codes einfach das Makro eingesetzt. Das definieren des Makros erzeugt keinen Code!
| |
| | |
| * .include
| |
| Ersetzt an dieser stelle, den Code, welcher über dem includebefehl geladen wird:
| |
| .include "ZusätzlicherSource.s"
| |
| | |
| * Labels in Makros
| |
| Problematisch wird es, wenn in einem Makro Labels definiert wurden. Wird das Makro zweimal verwendet, so wird der Assembler melden, dass die Labels doppelt vergeben wurden. Dies ist nicht erlaubt. Alternativ, was der Assembler erlaubt, sind Zahlen als Label, die auch mehrfach verwendet werden können. Um auf die richtigen Labels zu zeigen, wird bei der Sprunganweisung einfach der Zahl "f" angehängt, wenn das Label nach diesem Sprung angesprochen werden soll. Ein "b", wenn das Label zuvor angesprochen werden soll.
| |
| | |
| * Praktische Makros
| |
| .macro push1 register
| |
| str \register,[sp,#-16]!
| |
| .endm
| |
| .macro push2 register1,register2
| |
| stp \register1,\register2,[sp,#-16]!
| |
| .endm
| |
| .macro pop1 register
| |
| ldr \register,[sp],#16
| |
| .endm
| |
| .macro pop2 register1,register2
| |
| ldp \register1,\register2,[sp],#16
| |
| .endm
| |
| | |
| == Linux Systemaufrufe ==
| |
| https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
| |
| https://github.com/ilbers/linux/blob/master/arch/sh/include/uapi/asm/unistd_64.h
| |
| | |
| x0–x7: Parameterübergabe
| |
| x8: SystemCallNummer
| |
| svc 0 -> Aufruf des Systemcalls
| |
| x0: Ergebniss
| |
| | |
| Wenn x0 negativ -> negieren -> dann Fehler:
| |
| https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h
| |
| https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h
| |
| | |
| In der Parameterübergabe erwarten viele SystemCalls eine Struktur. Ein einfacher SystemCall wie nanosleep wird als C-Code wie folgt aufgerufen:
| |
| int nanosleep(const struct timespec *req, struct timespec *rem);
| |
| Dieser Aufruf erwartet jeweils eine Struktur "timespec", die in x0 und x1 übergeben wird.
| |
| Diese Struktur ist wie folgt definiert:
| |
| struct timespec {
| |
| time_t tv_sec; /* seconds */
| |
| long tv_nsec; /* nanoseconds */
| |
| };
| |
| Nach Assembler:
| |
| timespec:
| |
| timespec_tv_sec: .dword 0
| |
| timespec_tv_nsec: .dword 100000000
| |
| Um es der Funktion zu übergeben:
| |
| ldr x0,=timespec
| |
| ldr x1,=timespec
| |
| | |
| * Arbeiten mit Dateien
| |
| https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/fcntl.h
| |
| Datei öffnen:
| |
| mov x0,#-100
| |
| ldr x1,=Dateiname
| |
| mov x2,#flags //O_RDONLY, O_WRONLY, =O_CREAT
| |
| mov x3,#666
| |
| mov x8,#323 //#define __NR_openat 323
| |
| svc 0
| |
| Rückgabe in x0 ->
| |
| adds x10,XZR,x0 //file descriptor nach x10
| |
| bpl ok //Wenn positiv, dann hat es geklappt
| |
| Fehler...
| |
| | |
| Datei schließen:
| |
| mov x0,x10 //Beispiel x10 für file descriptor
| |
| mov x8,#118 //#define __NR_fsync 118
| |
| svc 0
| |
| mov x0,x10 //Beispiel x10 für file descriptor
| |
| mov x8,#6 //#define __NR_close 6
| |
| svc 0
| |
| | |
| Lesen aus einer Datei:
| |
| mov x0,x10 //Beispiel x10 für file descriptor
| |
| ldr x1,=buffer //Hierhin sollen die Daten geladen werden
| |
| mov x2,#256 //Max Anzahl an Bytes, die geladen werden sollen (Beispiel: 256Bytes)
| |
| mov x8,#3 //#define __NR_read 3
| |
| svc 0
| |
| | |
| Speichern in eine Datei:
| |
| mov x0,x10 //Beispiel x10 für file descriptor
| |
| ldr x1,=buffer //Von hier sollen die Daten gespeichert werden
| |
| mov x2,#256 //Anzahl an Bytes, die gespeichert werden sollen (Beispiel: 256Bytes)
| |
| mov x8,#4 //#define __NR_write 4
| |
| svc 0
| |
| | |
| | |
| == GPIO Programmierung ==
| |
| * Mit Linux (Syscalls):
| |
| Die Pins werden wie Dateien behandelt. Der Dateipfad ist: /sys/class/gpio
| |
| Es wird einfach ein String, der den PIN angibt nach /sys/class/gpio/export geschrieben.
| |
| Der Treiber erstellt dann vier Dateien:
| |
| * /sys/class/gpio/gpio17/direction: Wird verwendet, um anzugeben, ob der Pin für Eingabe oder Ausgabe ist
| |
| * /sys/class/gpio/gpio17/value: Wird verwendet, um den Wert des Pins festzulegen oder zu lesen
| |
| * /sys/class/gpio/gpio17/edge: Wird verwendet, um einen Interrupt festzulegen, um Wertänderungen zu erkennen
| |
| * /sys/class/gpio/gpio17/active_low: Wird verwendet, um die Bedeutung von 0 und 1 umzukehren
| |
| | |
| !!!GPIO17 ändern!!!!!!!!!!!!! und testen
| |
| Nach dem Beenden wird das Gerät mit dem File "/sys/class/gpio/unexport" und dem String des PINs geschlossen.
| |
| | |
| .text
| |
| nanosleep:
| |
| ldr x0,=timespec
| |
| ldr x1,=timespec
| |
| mov x8,#162 //#define __NR_nanosleep 162
| |
| svc 0
| |
| .data
| |
| timespec:
| |
| timespec_tv_sec: .dword 0
| |
| timespec_tv_nsec: .dword 100000000
| |
| | |
| *PIN Öffnen:
| |
| .text
| |
| mov x0,#-100
| |
| ldr x1,=gpioexp
| |
| mov x2,#1 //#define O_WRONLY 00000001
| |
| mov x3,#666 //Dateirechte (Jeder darf alles...)
| |
| mov x8,#323 //#define __NR_openat 323
| |
| svc 0
| |
| mov x10,x0 //File Descriptor
| |
| ldr x1,=pin
| |
| mov x2,#2
| |
| mov x8,#4 //#define __NR_write 4
| |
| svc 0
| |
| mov x0,x10
| |
| mov x8,#118 //#define __NR_fsync 118
| |
| svc 0
| |
| mov x0,x10
| |
| mov x8,#6 //#define __NR_close 6
| |
| svc 0
| |
| .data
| |
| gpioexp: .asciz "/sys/class/gpio/export"
| |
| pin: .asciz "17" //Ändern!!!!!!!!!!!!!
| |
| | |
| GPIODirectionOut pin17:
| |
| | |
| | |
| Muss ich probieren! Seite 180 im Buch
| |
| | |
| | |
| === Näher an die direkte Programmierung ===
| |
| In Bare Metal, siehe Kurs hier, werden Geräte über Adressen angesprochen. Diese werden laut Dokumentation des Braodcom-Chips als Register bezeichnet. Dies hat allerdings nichts mit den ARM-Registern zu tun. Hier sind die Speicheradressen gemeint.
| |
| Leider ist die Dokumentation nicht wirklich gut, so dass wir diese Adressen hier im Linux-System heraussuchen müssen. Dies ist über folgenden Befehl in der Konsole möglich:
| |
| | |
| dmesg
| |
| genauer dmesg | grep gpio //Sucht nur die Info für gpio heraus...
| |
| | |
| == Interaktion mit C ==
| |
| === C Funktion aufrufen aus Assembler ===
| |
| === Assembler-Funktion in C aufrufen ===
| |
| === Inline-Assembler in C ===
| |
| === Bibliotheken verwenden ===
| |
| | |
| == Multiplizieren, Dividieren und Akkumulation ==
| |
| === Multiplizieren ===
| |
| mul xd, xn, xm
| |
| Dies multipliziert den Wert aus "xn" mit dem Wert aus "xm". Das Ergebnis wird in "xd" zurückgegeben.
| |
| Die Mathematiker sehen hier das Problem: Es werden zwei 64-Bit Werte multipliziert, aber das Ergebnis wird in 64-Bit abgelegt.
| |
| Dies bedeutet:
| |
| * Ergebnis größer 64-Bit, wird auf 64-Bit reduziert. Damit stimmt das Ergebnis nicht
| |
| * Es werden "nur" 64-Bit Register für xn und xm akzeptiert
| |
| * Es gibt keine Anzeige des Überlaufs
| |
| | |
| Zusätzliche Befehle:
| |
| smull xd,wn,wm // Multipliziere mit Vorzeichen wn mit wm
| |
| umull xd,wn,wm // Multipliziere ohne Vorzeichen wn mit wm
| |
| | |
| Ein 128-Bit Ergebnis zu erhalten können folgende Befehle verwendet werden:
| |
| smulh xd,xn,xm // Multipliziere mit Vorzeichen xn mit xm und schreibe das obere Ergebnis (65-128 Bit) nach xd
| |
| umulh xd,xn,xm // Multipliziere ohne Vorzeichen xn mit xm und schreibe das obere Ergebnis (65-128 Bit) nach xd
| |
| | |
| In der Kombination von mul und smulh oder umulh können 128-Bit-Ergebnisse erzeugt werden.
| |
| | |
| Weitere Befehle:
| |
| mneg xd,xn,xm // xd = -(xn * xm)
| |
| smnegl xd,wn,wm // xd = -(wn * wm) mit Vorzeichen
| |
| umnegl xd,wn,wm // xd = -(wn * wm) ohne Vorzeichen
| |
| | |
| Beispiele:
| |
| mov x0, #25
| |
| mov x1, #4
| |
| | |
| mul x2,x0,x1 // x2 = 100
| |
| mneg x2,x0,x1 // x2 = -100
| |
| smull x2,x0,x1 // x2 = 100
| |
| smnegl x2,x0,x1 // x2 = -100
| |
| umull x2,x0,x1 // x2 = 100
| |
| umnegl x2,x0,x1 // x2 = -100
| |
| | |
|
| |
| | |
| | |
| | |
| | |
|
| |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| * Interaktion mit anderen Programmiersprachen
| |
| * Zugriff auf Hardwaregeräte
| |
| * Anweisungen für den Gleitkommaprozessor
| |
| * Anweisungen für den NEON-Prozessor
| |