Raspberry Pi 3 (aarch64) in Assembler programmieren



  • Ich stricke das jetzt mal um. Wie ich gesehen habe, kann man mit asm(); ja aus C heraus mit den Registern und so spielen. Das funktioniert auch ganz gut wie ich sehe. Allerdings habe ich das jetzt noch nicht als Bare Metal gemacht, da ich überhaupt mal sehen wollte, ob es funktioniert.

    Tut es, nur ist mir etwas aufgefallen.

    void los()
    {
        char begin[] = "Hallo Welt mit Syscall!\n";
    
        asm("mov x8, #64");
        asm("mov x0, #1");
        asm("mov x2, #24");
        asm("svc 0");
    }
    
    

    Die Funktion soll ganz banal einen Syscall aufrufen.

    In x8 steht, welcher Syscall das sein soll. 64 ist write.
    In x0 steht der fd
    in x2 die Länge der Ausgabe
    Und svc 0 feuert das Ganze ab.

    So. Funktioniert hervorragend. Auch mit GDB sehe ich wunderschön, wie jede Zeile ausgeführt wird und genau macht was sie soll. Mit einer Ausnahme!

    char begin[] = "Hallo Welt mit Syscall!\n";

    wird direkt in x1 geschrieben. Dort soll es auch hin, aber woher weiss der Compiler das? Ich sage dem nirgendwo, was ich mit der Variable machen will. Das verwirrt mich mal wieder :(. Ich finde auch keine Erklärung dazu.



  • @Hirnfrei sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    wird direkt in x1 geschrieben. Dort soll es auch hin, aber woher weiss der Compiler das? Ich sage dem nirgendwo, was ich mit der Variable machen will. Das verwirrt mich mal wieder :(. Ich finde auch keine Erklärung dazu.

    Das könnte sehr gut Zufall sein. Eigentlich gibt es ja keinen Grund, dass der Compiler mit begin überhaupt irgendwas macht. Das ist nur eine lokale Variable, die nicht weiter verwendet wird, die kann eigentlich komplett ignoriert werden. Könnte es vielleicht sein, dass du keine Optimierungen aktiviert hattest? Lokale Variablen werden normalerweise auf den Stack gespeichert oder in Registern. Vielleicht ist x1 einfach nur der Ort, an dem der Compiler das begin in diesem Fall ablegt, wenn es nicht rausoptimiert wurde.

    Der Code ist IMHO auf jeden Fall nicht korrekt, auch wenn er funktioniert. Du solltest in einem asm-Block auf jeden Fall explizit x1 setzen.



  • @Finnegan sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Das könnte sehr gut Zufall sein. Eigentlich gibt es ja keinen Grund, dass der Compiler mit begin überhaupt irgendwas macht. Das ist nur eine lokale Variable, die nicht weiter verwendet wird, die kann eigentlich komplett ignoriert werden. Könnte es vielleicht sein, dass du keine Optimierungen aktiviert hattest? Lokale Variablen werden normalerweise auf den Stack gespeichert oder in Registern. Vielleicht ist x1 einfach nur der Ort, an dem der Compiler das begin in diesem Fall ablegt, wenn es nicht rausoptimiert wurde.

    Richtig. Ich optimiere da gar nichts. Zumindest nicht bewusst. Es ist eben wirklich interessant, dass es ausgerechnet bei X1 gespeichert wird. X0 würde nichts bringen, genauso wenig wie X8 und X2. Ausserdem wird es direkt in der entsprechenden Zeile in X1 gesetzt. Da könnte man also nicht einmal annehmen, es wäre weil der Compiler merkt was ich da vorhabe. Mysteriös.

    Der Code ist IMHO auf jeden Fall nicht korrekt, auch wenn er funktioniert. Du solltest in einem asm-Block auf jeden Fall explizit x1 setzen.

    Wenn ich jetzt zum Beispiel:

    asm("ldr x1, =r" : (begin));
    

    einbaue, jammert der Compiler etwas wegen "string literal". Also wie ich jetzt die Variable da rein bekomme, da bin ich noch nicht ganz firm.



  • Falls der Compiler für asm AT&T-Syntax (wie z.B. der gcc) verwendet, dann schau mal in die Antwort von manipulating c variable via inline assembly.



  • @Th69 sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Falls der Compiler für asm AT&T-Syntax (wie z.B. der gcc) verwendet, dann schau mal in die Antwort von manipulating c variable via inline assembly.

    Super. Das hat mir auf jeden Fall schon etwas geholfen. Funktioniert aber leider nur mit Integer.



  • @Hirnfrei sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    @Th69 sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Falls der Compiler für asm AT&T-Syntax (wie z.B. der gcc) verwendet, dann schau mal in die Antwort von manipulating c variable via inline assembly.

    Super. Das hat mir auf jeden Fall schon etwas geholfen. Funktioniert aber leider nur mit Integer.

    Auch ein Pointer ist aus Assembler-Perspektive ein Integer. Oder geht es hier noch um irgendwas andereres, wie einen Float? Man möge mich korrigieren, aber was verschiedene Datentypen in Registern angeht, sind mir in Assembler eigentlich nur (signed/unsigned) Integer und Floats unterschiedlicher Breite untergekommen. Und auch wenn ich das noch nicht gebraucht habe, sollten sich letztere denke ich mal auch in einem inline-Assembly-Block initialisieren lassen. Im Zweifel via (reinterpret-) Cast auf einen Integer er gleichen Breite. Bei Registern sind m.E. nur die Bits ausschlaggebend, die a drin stehen.



  • @Finnegan sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Auch ein Pointer ist aus Assembler-Perspektive ein Integer. Oder geht es hier noch um irgendwas andereres, wie einen Float? Man möge mich korrigieren, aber was verschiedene Datentypen in Registern angeht, sind mir in Assembler eigentlich nur (signed/unsigned) Integer und Floats unterschiedlicher Breite untergekommen. Und auch wenn ich das noch nicht gebraucht habe, sollten sich letztere denke ich mal auch in einem inline-Assembly-Block initialisieren lassen. Im Zweifel via (reinterpret-) Cast auf einen Integer er gleichen Breite. Bei Registern sind m.E. nur die Bits ausschlaggebend, die a drin stehen.

    Aua. Klar, Zeiger = Speicheradresse. Der tat weh ;). Habe meinen Fehler eingesehen. Jetzt heisst es mit rumspielen :). Den Threat lasse ich aber mal auf, da kommen sicher noch Fragen.



  • @Hirnfrei sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Aua. Klar, Zeiger = Speicheradresse. Der tat weh ;). Habe meinen Fehler eingesehen. Jetzt heisst es mit rumspielen :). Den Threat lasse ich aber mal auf, da kommen sicher noch Fragen.

    Hehe, Jo. Assembler ist so low-level, da sind selbst so grundlegende Abstraktionen wie Typen etwas, dass man sich manchmal bewusst abgewöhnen muss 😉



  • @Finnegan sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    @Hirnfrei sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Aua. Klar, Zeiger = Speicheradresse. Der tat weh ;). Habe meinen Fehler eingesehen. Jetzt heisst es mit rumspielen :). Den Threat lasse ich aber mal auf, da kommen sicher noch Fragen.

    Hehe, Jo. Assembler ist so low-level, da sind selbst so grundlegende Abstraktionen wie Typen etwas, dass man sich manchmal bewusst abgewöhnen muss 😉

    So ist es. Ich hab mich ja schon schwer getan, bis ich das mit den Registern verstanden hatte. Da wird irgendein Wert i x8 gepackt dann einer in x0. Wtf, was das für ein Quatsch was bringt das.

    Dann der Aha Moment.

    Ah, der Wert vom Syscall kommt nach x1. Der Filedescripter nach x0, die Ausgabe nach x1, die Ausgabelänge nach x2 und dann mit svc 0 schiesst man das erst ab.

    Aber schau an, da lernt man dann wie so ein Prozessor funkitoniert! Zumindest im Prinzip. Finde ich super Spannend!

    Ich hab jetzt aber auch einen Fahrplan. Da ich jetzt einen HDMI Capture hab und somit auch auf meinem Computer sehe, was der Pi da macht, geht es jetzt richtig los mit dem Bare-Metal und auch nicht mehr mit qemu, sondern richtig auf dem Pi.

    1. Hallo Welt (wer hätte es gedacht
    2. Datei laden (kombination aus C mit asm())
    3. Framebuffer
    4. Hoffen das ich soweit komme 😉

    Ich muss aber mal doof fragen. Ich weiss genau, ich habe das schon einmal irgendwo gemacht oder gelesen. Man kann einen Zeiger doch auf eine bestimmte Adresse vom Speicher zeigen lassen, oder irre ich da?



  • @Hirnfrei sagte in [Raspberry Pi 3 (aarch64) in

    Ich hab jetzt aber auch einen Fahrplan. Da ich jetzt einen HDMI Capture hab und somit auch auf meinem Computer sehe, was der Pi da macht, geht es jetzt richtig los mit dem Bare-Metal und auch nicht mehr mit qemu, sondern richtig auf dem Pi.

    Das absolute Sahnehäubchen wäre natürlich noch, einen gdbserver-Stub in deinem Code ans laufen zu bekommen. Damit könnte man dann sogar Remote-Debuggen. Also in deinem Editor Breakpoints setzen, durch das Programm steppen und gleichzeitig via HDMI eine Ausgabe zu haben. Da kann ich aber keine Tips geben, wie man das genau angeht, das habe ich in der Form noch nie gemacht. Dafür müsste man dem gbserver beibringen, den seriellen Port des Raspi zu nutzen. Gut möglich aber, das es da schon fertige Lösungen gibt. Denke da lohnt sich eine Recherche, wenn du dein Projekt wirklich so durchziehen willst.

    1. Hallo Welt (wer hätte es gedacht
    2. Datei laden (kombination aus C mit asm())
    3. Framebuffer
    4. Hoffen das ich soweit komme 😉

    Das klingt ja schonmal nach nem Plan. Allerdings bin ich mir nicht so sicher, was du mit "Datei laden" meinst. Das Thema haben wir ja schonmal angesprochen. Du hast kein Dateisystem und auch noch keine libc. D.h. auch keine lowlevel-Funktionen, mit denen man irgendwie Dateien öffnen könnte (open/close/read/write). Ja, noch nichtmal ein malloc, d.h. es funktionieren derzeit nur statische Allokationen wie der funktionslokale "Hallo Welt"-String, oder irgendwelche Arrays fester Größe.

    Ich muss aber mal doof fragen. Ich weiss genau, ich habe das schon einmal irgendwo gemacht oder gelesen. Man kann einen Zeiger doch auf eine bestimmte Adresse vom Speicher zeigen lassen, oder irre ich da?

    Ich benötige sowas nur wirklich selten, da ich kaum so hardwarenahe Sachen mache, aber einen Zeiger auf eine feste Adresse zeigen zu lassen, müsste in etwa so gehen:

    unsigned char* memory = (unsigned char*) (0xabcd);
    

    Einfach ein Integer-Literal mit der gewünschten Adresse in einen Pointer casten. In C++ dann mit reinterpret_cast. Mit char-Pointern sollte das unproblematisch sein, beim wildem hin- un her-casten sollte man aber vorsichtig sein, da können eventuell Regeln zu Strict Aliasing und in C++ Regeln zur Lebenszeit und zu uninitialisierten Objekten reinspielen. Da kommt man schonmal schnell in UB-Territorium.

    Speicher, dessen Inhalt auch unabhägig von deinem Programm von der Hardware geändert werden kann, sollte zudem auch noch über einen volatile-Pointer angesprochen werden. Sonst könnte Compiler anfangen, Leseoperationen rauszupotimieren, weil der Speicher innerhalb des C-Programms ja gar nicht modifiziert wurde. Das wäre dann für Pointer, über die du mit der Hardware kommunizierst und diese dir auch antwortet, indem sie in den Speicher schreibt. Für einen Framebuffer selbst sollte man das aber noch nicht benötigen. Wohl aber für Pointer auf irgendwelche Hardware-Register.



  • @Finnegan sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    @Hirnfrei sagte in [Raspberry Pi 3 (aarch64) in

    Das absolute Sahnehäubchen wäre natürlich noch, einen gdbserver-Stub in deinem Code ans laufen zu bekommen. Damit könnte man dann sogar Remote-Debuggen. Also in deinem Editor Breakpoints setzen, durch das Programm steppen und gleichzeitig via HDMI eine Ausgabe zu haben. Da kann ich aber keine Tips geben, wie man das genau angeht, das habe ich in der Form noch nie gemacht. Dafür müsste man dem gbserver beibringen, den seriellen Port des Raspi zu nutzen. Gut möglich aber, das es da schon fertige Lösungen gibt. Denke da lohnt sich eine Recherche, wenn du dein Projekt wirklich so durchziehen willst.

    Da ist qemu ja wirklich von Vorteil. Da kann ich das starten. Mache ich auch schon die ganze Zeit so. Hat schon Recht, dass wäre echt der Knaller!

    Das klingt ja schonmal nach nem Plan. Allerdings bin ich mir nicht so sicher, was du mit "Datei laden" meinst. Das Thema haben wir ja schonmal angesprochen. Du hast kein Dateisystem und auch noch keine libc. D.h. auch keine lowlevel-Funktionen, mit denen man irgendwie Dateien öffnen könnte (open/close/read/write). Ja, noch nichtmal ein malloc, d.h. es funktionieren derzeit nur statische Allokationen wie der funktionslokale "Hallo Welt"-String, oder irgendwelche Arrays fester Größe.

    Nimm "Datei laden" nicht zu wörtlich. Eben den Inhalt einer Datei da rein bekommen. Ob jetzt fest eingelötet, wie du mir den Link geschickt hast, oder über Dateisystem, oder Bytes von dem Datenträger lesen, oder eigens Filesystem bauen, spielt da jetzt keine wirklich Rolle. Ich meine damit einfach nur, den Inhalt einer Datei in das (wie nennt man das jetzt eigentlich? OS? Bare-Metal-OS?) Programm zu bekommen um damit zu arbeiten. Auf welchem Weg lasse ich einfach mal offen.

    Genau genommen weiss ich auch noch gar nicht, wofür ich es benutzen werden. Ideen habe ich da viele. Vielleicht so eine Art GrundOS bauen, was sich dann aus einer spezifischen Datei das eigenliche Programm holt? Oder, was ich mir gerade eher vorstellen könnte, wäre Einstellungen für den ODB2 damit zu setzen, ohne gleich das Programm neu bauen zu müssen. Nicht jedes Steuergerät gibt die gleichen Daten raus. So könnte ich das selbe Programm auch einfach für mein zweites Auto verwernden. Als Beispiel jetzt mal. Ich will eben einfach die Möglichkeit haben, da auch Daten laden zu können. Wobei speichern auch cool wäre. GPS Daten als Fahrtenbuch oder so.

    Oh je, bei mir ist gerade eine Idee eingeschlagen ... Ich hab schon ein Video gesehen, wo sehr gut die Nutzung vom Mini-UART demonstriert wird. Ich könnte meinem Pi ja ein Floppy bauen :). Als ein SD.-Card Lesegerät. Mit einem Arduino ist das auslesen und speichern auf eine SD-Karte ja easy. Hab ich schon oft gemacht auch formatieren, Daten auslesen und das alles. Die Daten könnte ich dann ja zum Pi über UART schaufeln.

    Boah. Entdecke die Möglichkeiten!

    Ich benötige sowas nur wirklich selten, da ich kaum so hardwarenahe Sachen mache, aber einen Zeiger auf eine feste Adresse zeigen zu lassen, müsste in etwa so gehen:

    unsigned char* memory = (unsigned char*) (0xabcd);
    

    Einfach ein Integer-Literal mit der gewünschten Adresse in einen Pointer casten. In C++ dann mit reinterpret_cast. Mit char-Pointern sollte das unproblematisch sein, beim wildem hin- un her-casten sollte man aber vorsichtig sein, da können eventuell Regeln zu Strict Aliasing und in C++ Regeln zur Lebenszeit und zu uninitialisierten Objekten reinspielen. Da kommt man schonmal schnell in UB-Territorium.

    Das teste ich mal.. Klarr, vorsichtig muss man da sein. Würde ich auch nur dann nutzen, wenn es notwendig ist. Siehe unten.

    Speicher, dessen Inhalt auch unabhägig von deinem Programm von der Hardware geändert werden kann, sollte zudem auch noch über einen volatile-Pointer angesprochen werden. Sonst könnte Compiler anfangen, Leseoperationen rauszupotimieren, weil der Speicher innerhalb des C-Programms ja gar nicht modifiziert wurde. Das wäre dann für Pointer, über die du mit der Hardware kommunizierst und diese dir auch antwortet, indem sie in den Speicher schreibt. Für einen Framebuffer selbst sollte man das aber noch nicht benötigen. Wohl aber für Pointer auf irgendwelche Hardware-Register.

    Prinzipiell geht es mir auch nur darum. Wenn ich bei Assembler eins gelernt habe, dann das Hardware immer irgendwo in den Speicher gemampt ist und den muss man ja dann auch ansprechen können.


Anmelden zum Antworten