Raspberry Pi 3 (aarch64) in Assembler programmieren



  • Mahlzeit!

    Mich hat irgendwie das Assembler-Feeling gepackt. Mit ein Grund dafür war eigentlich ein vorgeschlagenes Video auf Youtube, wo einer mit ich glaube 20 Zeilen Code einen eigenen Kernel geschrieben hat. Der konnte zwar nur eine Zeile Text ausgeben, aber irgendwie fand ich das total spannend.

    Ich würde jetzt gerne ein bisschen mit meinem Pi experimentieren. Der eignet sich meiner Meinung nach besonders gut, da er eine feste Hardware hat. Meine ersten Gehversuche mit Assembler und dem Pi, bzw. der Emulierung mittels qemu, waren bislang auch erfolgreich und ich behaupte mal, zumindest das Grundlegende von Assembler schon so halbwegs verstanden zu haben. Hallo Welt ausgeben zu lassen, ist auf jeden Fall bislang kein Problem und wie gesagt, was da passiert und was warum funktioniert leuchtet mir ein.

    Tutorials zu Assembler finden sich ja auch Tonnenweise. Aber, die lassen sich anscheinend nicht direkt mit aarch64-linux-gnu umsetzen. Simples push und pop für den Stack habe ich da zum Beispiel noch nicht zum laufen bekommen.

    Auch das öffnen einer Datei samt auslesen eines Textes will noch nicht klappen. Das Programm wird zwar übersetzt und kann auch ausgeführt werden, doch liefert es kein Ergebnis.

    Hat hier jemand Ahnung von Assembler und aarch64? Ich bin für jeden Hilfe dankbar. Egal ob gutes Tutorial, Beispielquelltexte, oder was auch immer.



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

    Mahlzeit!

    Mich hat irgendwie das Assembler-Feeling gepackt. Mit ein Grund dafür war eigentlich ein vorgeschlagenes Video auf Youtube, wo einer mit ich glaube 20 Zeilen Code einen eigenen Kernel geschrieben hat. Der konnte zwar nur eine Zeile Text ausgeben, aber irgendwie fand ich das total spannend.

    Das war ein bare-metal Programm. Ein Kernel beinhaltet mindestens CPU Setup, Memory Management/Setup und Interrupt/Trap handling, und das ist in 20 Zeilen nicht machtbar (in 512 Bytes Maschinencode? ja vielleicht).

    Tutorials zu Assembler finden sich ja auch Tonnenweise. Aber, die lassen sich anscheinend nicht direkt mit aarch64-linux-gnu umsetzen. Simples push und pop für den Stack habe ich da zum Beispiel noch nicht zum laufen bekommen.

    Reden wir hier von bare-metal Programmen oder von in Assembler geschriebenen Anwendung, die ganz als normal als ein ELF Binary unter Linux laufen? Beim Ersten musst du dich mit Linkerscriptsprache beschäftigen und dann in einen Linkerscript beschreiben, wie dein Objectcode aufgeteilt (bzw im Speicher abgelegt) werden soll.

    Auch das öffnen einer Datei samt auslesen eines Textes will noch nicht klappen. Das Programm wird zwar übersetzt und kann auch ausgeführt werden, doch liefert es kein Ergebnis.

    Ich nehme mal du implementierst nicht die komplette Funktionalität für das Lesen von Dateien in Assembler. Rufst du per call Funktionen aus der C Laufzeitumgebung auf oder nutzt du direkt Syscalls vom Kernel?

    Hat hier jemand Ahnung von Assembler und aarch64? Ich bin für jeden Hilfe dankbar. Egal ob gutes Tutorial, Beispielquelltexte, oder was auch immer.

    Also reinrassige ARMv8 Assembler Tutorials/Howtos sind eher selten. Aber es gibt das eine oder andere Buch. Da wäre zum Beispiel Modern Arm Assembly Language Programming, dass aarch32, aarch64 und sämtliches SIMD/VFPU Zeug abdeckt. Ich fand es ganz okay, ist allerdings nicht ganz ohne. Und mit etwas Glück findest du es auch als PDF. 😉



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

    Das war ein bare-metal Programm. Ein Kernel beinhaltet mindestens CPU Setup, Memory Management/Setup und Interrupt/Trap handling, und das ist in 20 Zeilen nicht machtbar (in 512 Bytes Maschinencode? ja vielleicht).

    Bevor ich mich hier jetzt um Kopf und Kragen rede, hier das Video. Wenn das Bare Metal ist, dann ist es Bare Metal. Ich bin noch nicht firm genug um irgendwie zu behaupten, es wäre ein Kernel ;).

    Reden wir hier von bare-metal Programmen oder von in Assembler geschriebenen Anwendung, die ganz als normal als ein ELF Binary unter Linux laufen? Beim Ersten musst du dich mit Linkerscriptsprache beschäftigen und dann in einen Linkerscript beschreiben, wie dein Objectcode aufgeteilt (bzw im Speicher abgelegt) werden soll.

    Also im Moment bin ich noch bei ELF Dateien. Ich denke mal, man sollte erst krabbeln lernen, bevor man anfängt zu laufen. Aber schlussendlich wäre es mir schon sehr Recht, wenn es dann auf bare-metal hinauslaufen würde.

    Ich nehme mal du implementierst nicht die komplette Funktionalität für das Lesen von Dateien in Assembler. Rufst du per call Funktionen aus der C Laufzeitumgebung auf oder nutzt du direkt Syscalls vom Kernel?

    Nein, ich will das mit Syscalls machen. Sonst könnte ich es ja gleich in C schreiben ;). Man mag mich da jetzt als verrückt erklären, aber dieses verschieben in Registern und das alles, finde ich gerade irgendwie voll ansprechend!

    Also reinrassige ARMv8 Assembler Tutorials/Howtos sind eher selten. Aber es gibt das eine oder andere Buch. Da wäre zum Beispiel Modern Arm Assembly Language Programming, dass aarch32, aarch64 und sämtliches SIMD/VFPU Zeug abdeckt. Ich fand es ganz okay, ist allerdings nicht ganz ohne. Und mit etwas Glück findest du es auch als PDF. 😉

    Meinst du das hier?

    Das findet sich garantiert auch als PDF. Da ich aber selbst Bücher schreibe und es eigentlich nicht so lustig finde, wenn man meine Bücher für Lau runterladen kann, werde ich das dann doch eher kaufen 🙂



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

    Bevor ich mich hier jetzt um Kopf und Kragen rede, hier das Video. Wenn das Bare Metal ist, dann ist es Bare Metal. Ich bin noch nicht firm genug um irgendwie zu behaupten, es wäre ein Kernel ;).

    Was zum?!? Der Typ Programmiert nicht, der macht Speicherpoking. Der schreibt einfach fertigen Maschinencode an die entsprechenden Stellen im Speicher und hat vermutlich keinen blassen Schimmer, was die einzelnen Opcodes machen. Und das hat auch nichts mit ARM zu tun, hlt ist ein x86 Opcode. Und bei den Werten, die er in den Speicher schreibt, taucht vor dem hlt nicht mal ein 0xFA auf, was der cli Opcode wäre. Bevor man die CPU anhält sollte man die Interrupts abschalten.

    Also im Moment bin ich noch bei ELF Dateien. Ich denke mal, man sollte erst krabbeln lernen, bevor man anfängt zu laufen. Aber schlussendlich wäre es mir schon sehr Recht, wenn es dann auf bare-metal hinauslaufen würde.

    Ja, spätestens dann muss du dich mit Linkerscripten auseinandersetzen und musst auch schauen welche Codesegemente dein Assembler/Compiler generiert. Gerade moderne Compiler trennen hier sauber zwischen exec, bss, data, read only data und so weiter. Und im Gegensatz zu diesen komischen Youtuber würde ich dafür nicht nasm nehmen, sondern wirklich die GNU tools. Das ist echt gruselig, was der da macht.

    Nein, ich will das mit Syscalls machen. Sonst könnte ich es ja gleich in C schreiben ;). Man mag mich da jetzt als verrückt erklären, aber dieses verschieben in Registern und das alles, finde ich gerade irgendwie voll ansprechend!

    Nein nein, ist schon in Ordnung. Habe das auch alles mal gemacht. Lass mich schnell mal gucken, ich habe bestimmt irgendwo noch was rumliegen. Hmm, ja, ich kann dir ein x86 Multiboot-spec 32->64 Stub anbieten. Den braucht man, wenn man aus GRUB heraus direkt in einen 64Bit Kernel springen will. Stub, 64Bit Kernel Entry, Linker Script und Makefile. Es sollte eigentlich noch alles gehen, ich habe das vor ca 10 Jahren geschrieben. Ich biete dir das an, weil bare-metal dank dem 16Bit Legacy Code im BIOS/UEFI auf x86 deutlich einfacher ist. Da kann man zur Not für Ausgaben auf den langsamen Code zurückgreifen und muss nicht wie bei ARM den UART/Mini-UART (für serielle Console) oder gar GPU Ansteuerungscode selbst schreiben.

    Meinst du das hier?

    Das findet sich garantiert auch als PDF. Da ich aber selbst Bücher schreibe und es eigentlich nicht so lustig finde, wenn man meine Bücher für Lau runterladen kann, werde ich das dann doch eher kaufen 🙂

    Ja genau, dass sind die Daniel Kusswurm Bücher. Der Mann hat haufenweise solche Bücher geschreiben. Diese Bücher gab es mal für Lau im Rahmen einer Aktion, so wie es mal den ganzen Batzen Cryptobücher vor ein paar Jahren frei als PDF gab.



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

    Was zum?!? Der Typ Programmiert nicht, der macht Speicherpoking. Der schreibt einfach fertigen Maschinencode an die entsprechenden Stellen im Speicher und hat vermutlich keinen blassen Schimmer, was die einzelnen Opcodes machen. Und das hat auch nichts mit ARM zu tun, hlt ist ein x86 Opcode. Und bei den Werten, die er in den Speicher schreibt, taucht vor dem hlt nicht mal ein 0xFA auf, was der cli Opcode wäre. Bevor man die CPU anhält sollte man die Interrupts abschalten.

    Ganz ruhig, aufregen ist schlecht für die Pumpe ;). Ich hab keine Ahnung was der sonst noch so gemacht hat. Ich hab das Video gesehen und seitdem habe ich Lust mit Assembler und eben Bare-Metal rum zuspielen. Wirklich gut finde ich das Video nicht. Ich will mir kein Urteil über den Youtuber erlauben, aber warum man sich bei einem Video übers Programmieren formatfüllend ins Bild setzen muss und dann gelegentlich mal transparent den Bildschirm zeigen muss, erschliesst sich mir nicht. Ich habe schon viele Videos zum programmieren und so gesehen und da war nie der Programmierer mit im Bild und schon gar nicht formatfüllend.

    Also, der ist der Grund warum mich das interessiert, ansonsten können wir ihn ausklammern oder nach /dev/null verschieben ;). Ist nicht abwertend oder böse gemeint, es trifft nur einfach nicht meinen Geschmack.

    Ja, spätestens dann muss du dich mit Linkerscripten auseinandersetzen und musst auch schauen welche Codesegemente dein Assembler/Compiler generiert. Gerade moderne Compiler trennen hier sauber zwischen exec, bss, data, read only data und so weiter. Und im Gegensatz zu diesen komischen Youtuber würde ich dafür nicht nasm nehmen, sondern wirklich die GNU tools. Das ist echt gruselig, was der da macht.

    Aber nein. Ich verwende derzeit aarch64-linux-gnu-*. Linkterscripte hab ich auch schon gemacht, hat auch funktioniert bislang. Es gibt ja für vieles gute Tutorials nur eben, wie man Dateien öffnet, ausliest und so, da habe ich für ARM noch nichts gefunden. Es gibt ein paar Dinge wo mir anscheinend das grundlegende Verständnis noch fehlt. Aber, ich bin ja auch erst am Anfang.

    Nein nein, ist schon in Ordnung. Habe das auch alles mal gemacht. Lass mich schnell mal gucken, ich habe bestimmt irgendwo noch was rumliegen. Hmm, ja, ich kann dir ein x86 Multiboot-spec 32->64 Stub anbieten. Den braucht man, wenn man aus GRUB heraus direkt in einen 64Bit Kernel springen will. Stub, 64Bit Kernel Entry, Linker Script und Makefile. Es sollte eigentlich noch alles gehen, ich habe das vor ca 10 Jahren geschrieben. Ich biete dir das an, weil bare-metal dank dem 16Bit Legacy Code im BIOS/UEFI auf x86 deutlich einfacher ist. Da kann man zur Not für Ausgaben auf den langsamen Code zurückgreifen und muss nicht wie bei ARM den UART/Mini-UART (für serielle Console) oder gar GPU Ansteuerungscode selbst schreiben.

    Schaden kann es sicherlich nicht. Da bedanke ich mich schon einmal.

    Das mit UART und einem GPU Ansteuerungscode werde ich mir aber wohl noch drauf schaffen müssen. Das gehört ja auch dazu, die Hardware zu verstehen. Muss ja nicht gleich 3D sein.

    Ja genau, dass sind die Daniel Kusswurm Bücher. Der Mann hat haufenweise solche Bücher geschreiben. Diese Bücher gab es mal für Lau im Rahmen einer Aktion, so wie es mal den ganzen Batzen Cryptobücher vor ein paar Jahren frei als PDF gab.

    Gibt es für Kindle sogar für 0 Euro. Werde ich mal mein Unlimited auffrischen ;). Danke auf jeden Fall für den Tipp!

    So, aber ich habe dann jetzt mal noch eine Frage. Bare Metal, was genau bedeutet das jetzt? Anfangs war ich der Meinung, dass ist programmieren direkt auf die Hardware. Wenn ich mir aber die Syscalls vom ARM so anschaue sieht das doch sehr nach Linux aus. So irgendwie verwirrt mich das gerade und wenn ich danach suche kommen nur Bare Metal Server raus. Das ist ja nicht das was ich wissen will.



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

    Bevor ich mich hier jetzt um Kopf und Kragen rede, hier das Video. Wenn das Bare Metal ist, dann ist es Bare Metal. Ich bin noch nicht firm genug um irgendwie zu behaupten, es wäre ein Kernel ;).

    Hehe... 0xb8000 seh ich da grad beim durchzappen. Das ist die Speicheradresse, an der auf PCs der Videospeicher für den Standard-VGA-Textmodus gemappt ist. Sind schon spassig solche Spielereien. Das geht auch mit Grafikmodi (z.B. via VBE) - selbst auf modernen Kisten noch. Da kann man dann die Pixel direkt in den gemappten Videospeicher schreiben. Das ist die Brot-und-Butter-Grafik, die z.B. so ein frisch installiertes OS nutzt, wenn noch kein Grafiktreiber installiert wurde.

    Das ist aber in diesem Fall PC-spezifisch, ich würde mich also nicht darauf verlassen, dass das auf nem Raspi genau so läuft. Da kann man wahrscheinlich ähnliches machen, aber die Hardware dürfte da anders verdrahtet sein. D.h. die Videomodi sind andere, werden anders umgeschaltet und der Videospeicher ist wahrscheinlich auf anderen Adressen gemappt. Da müsste man sich erstmal schlau machen wie das auf diesem System läuft.

    Ich mache solche Spielereien gerne noch mit dem guten alten DOS (FreeDOS in QEMU), da hat man ein simples Betriebsystem, ohne dass einem der Lowlewel-Zugriff auf solche Hardware-Sachen verbaut ist. Unter nem ausgewachsenen OS müsste man das als Treiber oder Kernel-Modul implementieren, da man dort nicht einfach so BIOS-Funktionen aufrufen kann oder gar auf beliebige (physische) Speicheradressen zugreifen darf (Virtueller Adressraum). @VLSI_Akiko hatte mir mal Baremetal OS empfohlen - das ist eventuell auch eine Option, mir war das dann aber doch ein wenig zu Lowlevel, vor allem fehlte mir da ein simples Dateisystem.

    So, aber ich habe dann jetzt mal noch eine Frage. Bare Metal, was genau bedeutet das jetzt? Anfangs war ich der Meinung, dass ist programmieren direkt auf die Hardware. Wenn ich mir aber die Syscalls vom ARM so anschaue sieht das doch sehr nach Linux aus. So irgendwie verwirrt mich das gerade und wenn ich danach suche kommen nur Bare Metal Server raus. Das ist ja nicht das was ich wissen will.

    In ein Bare-Metal-Programm, wie es @VLSI_Akiko genannt hat bootet man direkt rein, und es läuft ohne ein Betriebssystem. Wenn man so will ist das Programm sein eigenes OS. Hardware direkt programmieren kann man auch unter einem ausgewachsenen OS, wenn man ein Kernel-Modul oder einen Treiber programmiert. Zu den "ARM-Syscalls" kann ich nicht viel sagen, aber das könnten durchaus auch Aufrufe von Firmware/BIOS-Funktionen sein die nicht unbedingt zu einem OS wie Linux gehören müssen.



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

    Hehe... 0xb8000 seh ich da grad beim durchzappen. Das ist die Speicheradresse, an der auf PCs der Videospeicher für den Standard-VGA-Textmodus gemappt ist. Sind schon spassig solche Spielereien. Das geht auch mit Grafikmodi (z.B. via VBE) - selbst auf modernen Kisten noch. Das ist die Brot-und-Butter-Grafik, die z.B. so ein frisch installiertes OS nutzt, wenn noch kein Grafiktreiber installiert wurde.

    Das ist aber in diesem Fall PC-spezifisch, ich würde mich also nicht darauf verlassen, dass das auf nem Raspi genau so läuft. Da kann man wahrscheinlich ähnliches machen, aber die Hardware dürfte da anders verdrahtet sein. D.h. die Videomodi sind andere, werden anders umgeschaltet und der Videospeicher ist wahrscheinlich auf anderen Adressen gemappt. Da müsste man sich erstmal schlau machen wie das auf diesem System läuft.

    Also so schlau bin ich mittlerweile schon, um zu wissen wo ich Adresse von Hardware und so finde ;). Keine Ahnung wie viel Stunden Video ich mir da seit heute Morgen rein gezogen habe, aber definitiv hat die Arbeit darunter gelitten ;). Ich merke aber immer wieder, dass ich viel von Assembler noch gar nicht wirklich verstehe. Aber wie lange beschäftige ich mich jetzt damit, eine Woche oder so. Als ich mit C/C++ angefangen habe und man auf einmal keine Befehle sondern nur noch Funktionen hatte, kam mir auch manches sehr spanisch vor. Bin noch in der Lernphase, aber es macht tatsächlich echt Spass! Dieses direkt in einen Speicher schreiben zu können, oder was auf den Stack werfen und so ein Gerümpel, alleine schon zu sehen, wo überall was im Speicher rum liegt, wie das mit den Registern ist und das so ein Prozessor auch nichts anderes macht, wie ein "Befehl" nach dem anderen durchzuackern. Nur eben verflucht schnell.

    Ich mache solche Spielereien gerne noch mit dem guten alten DOS (FreeDOS in QEMU), da hat man ein simples Betriebsystem, ohne dass einem der Lowlewel-Zugriff auf solche Hardware-Sachen verbaut ist. Unter nem ausgewachsenen OS müsste man das als Treiber oder Kernel-Modul implementieren, da man dort nicht einfach so BIOS-Funktionen aufrufen kann oder gar auf beliebige (physische) Speicheradressen zugreifen darf (Virtueller Adressraum). @VLSI_Akiko hatte mir mal Baremetal OS empfohlen - das ist eventuell auch eine Option, mir war das dann aber doch ein wenig zu Lowlevel, vor allem fehlte mir da ein simples Dateisystem.

    Mit dem Dateisystem bin ich mal noch gespannt wie das bei diesem Bare-Metal funktioniert. Ich sage aber mal so. Was ich mit vorstellen könnte, da werde ich so etwas nicht zwingend brauchen. Dafür aber USB und eben auch eine grafische Ausgabe.

    In ein Bare-Metal-Programm, wie es @VLSI_Akiko genannt hat, bootet man direkt rein, und es läuft ohne ein Betriebssystem. Wenn man so will ist das Programm sein eigenes Betriebssystem. Hardware direkt programmieren kann man auch unter einem OS, wenn man ein Kernel-Modul oder einen Treiber programmiert.

    Genau dieser Punkt ist für mich auch immer spannender, also dass das Programm so etwas wie sein eigenes Betriebssystem ist. Oder anders, dass da kein Betriebssystem geladen werden muss.

    Als ein Beispiel könnte ich den Bordcomputer von meinem Auto nehmen. Da bastele ich schon seit Jahren dran rum, mal fehlt aber Geld, dann Zeit, dann Lust. Hintergrund ist einfach, es ist ein Peugeot 106 S2 von 2000. Der hat nur eine Lampe für das Kühlwasser und die geht erst bei 118 Grad Celsius an, wo man dann eigentlich sofort den Motor ausmachen müsste. Um Kontrolle über die Temperatur zu haben, habe ich mir vor vielen Jahren einen USB-OBDII Stecker gekauft. Der funktioniert super und man kann ja so viele schöne Daten damit abrufen. Was wäre also naheliegender, als den mit dem PI verbinden, Display dran und dann einen eigenen Bordcomputer bauen?

    Blöd finde ich da aber von Anfang an, da laufen dann ja noch Programme mit, die ich wirklich nie brauche. Ausserdem dauert das booten auch relativ lange. Hänge ich den Pi ans Dauerplus spare ich mir zwar das Booten, dafür zieht er auf längere Zeit die Batterie leer, also müsste ich da eine Schaltung einbauen, die bei einem gewissen Spannungszustanden den Pi und den USB-Stecker abschaltet.

    Zugegeben, ich habe keinen Plan, wie hoch Arbeitsaufwand dafür ist. Ich hab mittlerweile auch einen OBD2 Stecker mit USB, wo die Verbinudng schneller und stabiler läuft. Den könnte ich theoretisch ja über UART anesteuern. Trotzdem kann ich nicht abschätzen, wie hoch der Aufwand wäre. Aber, wenn ich den Pi dann boote, dann wäre er wirklich ausschliesslich mein Bordcomputer. Kein Linux, keine sonstigen Prozesse, nichts. Nur mein Programm/OS. Das fände ich richtig, richtig cool!

    Ist aber auch wieder nur so ein Gedankengang jetzt von mir. Wie gesagt, ich habe keinen Plan von dem Aufwand, keine Ahnung, wie gut ich dafür Assembler können muss, keine Ahnung ob alles gut genug dokumentiert ist, um es auch wirklich umsetzen zu können. Quasi keine Ahnung von nichts im Moment. Übertrieben ausgedrückt. Von daher bleibe ich erst einmal dabei, mit Assembler dann den Pi und allgemein Computer besser zu verstehen und auf dem Weg dahin einfach ein bisschen Spass haben und neues lernen, sollen erst einmal meine Hauptgründe sein, mich mit dem Thema zu befassen.

    Ich hab auch vor ein paar Jahren aus Spass mal so etwas wie einen Computer mit Arduinos zusammengebaut. Wobei man jetzt nicht unbedingt das Wort Computer zu wörtlich nehmen sollte. Das war einfach ein Arduino Mega, den ich als Kontrolleinheit verwendet hatte. Daran angeschlossen waren mehrere Arduino Nanos. Zwei waren für die Displayausgabe zuständig, wobei eben auch zwei kleine OELDs angeschlossen waren. An jedem einer. Ein weiterer Nano habe ich für ein kleines Tastenfeld benutzt. 0-9 und Enter. Dann hatte ich noch einen Nano, der verschiedene Sensoren abgerufen hat. Ein Display hat das Menü angezeigt. 01 war die Innentemperatur, 02 Aussentemperatur usw. Der Aufwand war für den Nutzen abartig hoch und das Ding war genaugenommen nach der Fertigstellung mit einem Schlag uninteressant. Die Idee, die Umsetzung, dass Überwinden von Problemen und das alles, war der eigentliche Reiz für mich. Danach habe ich dann ein paar Mal geschaut, wie das Wetter draussen und drinnen ist, dann ist er zugestaubt und nach und nach habe ich die Teile dann wieder für etwas anderes verwendet. Kann gut sein, dass es hier genauso ist. Aber auf dem Weg habe ich damals vieles gelernt und das hat mir nicht geschadet.



  • Ich hab da eine spezifische Frage.

    In alle Tutorials, die ich bisher so gesehen habe, werden Daten auf den Stack mit push geworfen und mit pop wieder runtergeholt. SP wandert bei Push eine Adresse nach oben und bei Pop wieder eine nach unten.

    Bei aarch64 scheint das ja ganz anders zu laufen. Da sehe ich zum Beispiel

    sub sp, sp,[ #(8 * 14)
    str x1, [sp, #(8 * 11)]

    Damit würde ich, wenn ich es richtig verstanden habe, die Daten aus Register x1 an die 11. Stelle im Stack werfen. 14 Stellen hab ich vorher eingerichtet.

    Läuft das also bei aarch64 komplett anders, als man es bei x86 zum Beispiel macht? Oder bin ich nur zu doof um den richtigen Umgang mi push und pop zu verwenden? Denn beim Übersetzen kommt bei push und pop ein Fehler, weil unbekannt.

    Erweiterung:

    Ich will jetzt nicht behaupten das ich verstehe, warum man bei aarch64 den Stack auf diese Weise benutzt, aber es funktioniert auf jeden Fall.

    Da ich jemand bin, der auch gerne probiert und das Internet ja mein Freund ist, auch wenn ich im suchen echt mies bin, habe ich jetzt ein paar Dinge herausgefunden.

    Wenn ich zum Beispiel Speicher reservieren will, um darin etwas zu speichern, dann scheint das so zu funktionieren:

    .data
    test: .zero .byte 16

    Wenn ich es richtig verstanden habe, wird der Speicher mit dem Label test mit zero gefüllt und zwar 16 Bytes. Man darf mich gerne korrigieren, wenn ich das falsch verstanden habe.

    So. Da hätte ich jetzt gerne ASCII Code drin. Da scheitere ich. Ich habe keine Ahnung, wie ich da jetzt irgendwas rein bekomme. Kann mir da jemand helfen?



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

    Dieses direkt in einen Speicher schreiben zu können, oder was auf den Stack werfen und so ein Gerümpel, alleine schon zu sehen, wo überall was im Speicher rum liegt, wie das mit den Registern ist und das so ein Prozessor auch nichts anderes macht, wie ein "Befehl" nach dem anderen durchzuackern. Nur eben verflucht schnell.

    Bin gestern beim oberflächlichen Stöbern hierüber gestolpert, das könnte irgenwann beim raspi-experimentieren nützlich sein: https://elinux.org/RPi_Framebuffer. Das scheint mir ne grobe Anleitug zu sein, wie man mit "baremetal" auf dem Standard-Raspi nen Videomodus setzt und einen Framebuffer bekommt. Das liest sich allerdings so, als gäbe es für diesen Boradcom-Grafikchip keine öffentliche und freie Doku. Die Infos scheinen aus dem Quellcode des Linux-Grafiktreibers für den Chip zusammengeklaubt zu sein.

    Die Infos sind allerdings ziemlich mager - besonders im Vergleich zu dem, was z.B. für VGA-Grafik oder VBE auf dem PC öffentlich verfügbar ist. Vielleicht findest du ja irgendwo noch was besseres 😉

    Mit dem Dateisystem bin ich mal noch gespannt wie das bei diesem Bare-Metal funktioniert. Ich sage aber mal so. Was ich mit vorstellen könnte, da werde ich so etwas nicht zwingend brauchen. Dafür aber USB und eben auch eine grafische Ausgabe.

    Wenn du das zu Fuss machen willst dann könte das sehr mühselig werden. Da muss man wohl den Controllerchip, an dem das Speichergerät angeschlossen ist programmieren. Also USB, SATA oder was auf immer. Und selbst dann hast du noch kein wirkliches Dateisystem, sondern kannst nur auf Datenblöcke des Speichergeräts zugreifen. Da brauchts dann noch Code für ein Dateisystem... das sind Sachen, mit denen man sich viele Jahre befassen kann, wenn man Bock hat 😉

    Man kann aber durchaus auch ohne Dateisystem. Daten kann man z.B. auch in seine Binary packen und zusammen mit dem Code laden, wenns nicht zu viel wird.

    In alle Tutorials, die ich bisher so gesehen habe, werden Daten auf den Stack mit push geworfen und mit pop wieder runtergeholt. SP wandert bei Push eine Adresse nach oben und bei Pop wieder eine nach unten.

    Bei aarch64 scheint das ja ganz anders zu laufen. Da sehe ich zum Beispiel

    sub sp, sp,[ #(8 * 14)
    str x1, [sp, #(8 * 11)]

    Damit würde ich, wenn ich es richtig verstanden habe, die Daten aus Register x1 an die 11. Stelle im Stack werfen. 14 Stellen hab ich vorher eingerichtet.

    Ich hab nicht wirklich Ahnung von ARM64, aber das was ich beim groben Stöbern so gesehen habe, sieht mir so aus, als gäbe es da keine push/pop-Instuktionen. Das scheint tatsächlich so gemacht zu werden. Deinem Beispiel nach zu urteilen, bist du wohl auch auf diese Seite hier gestossen: https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/using-the-stack-in-aarch64-implementing-push-and-pop

    Da scheint ja schonmal einiges zum Stack erklärt zu werden, z.B. - was ich interessant fand - dass das die Adresse im SP-Register wohl immer 16-Byte-Alignment braucht, während man die Werte, die man auf den Stack schreibt, auch ein Offset relativ zu SP haben dürfen, dass dieses Alignment nicht unbedingt einhält. Das dürfte wohl mit einer der Gründe für dieses "Vorreservieren" von Stack sein.

    Scheinbar ist das hier das Analog zu push und pop wie man es von x86 her kennt:

    str   x0, [sp, #-16]!    // push {x0}
    ldr   x0, [sp], #16      // pop {x0}
    

    Da wird SP immer um 16 Bytes verschoben. Das ist nicht gerade speichersparsam, wenn man nur einzelne Register auf den Stack schiebt. Wenn man will, kann man die sich sicher auch in ASM-Makros packen die man "push" und "pop" nennt... ist aber wohl nicht die beste Methode - eben wegen den Alignment-Anforderungen von SP. Der Artikel erwähnt aber auch, ein anderes Register als SP für den Stack Pointer zu nehmen (und ein paar Fallgruben, wenn man das tut). Das könnte vielleicht eine Alternative sein.

    .data
    test: .zero .byte 16

    Wenn ich es richtig verstanden habe, wird der Speicher mit dem Label test mit zero gefüllt und zwar 16 Bytes. Man darf mich gerne korrigieren, wenn ich das falsch verstanden habe.

    So. Da hätte ich jetzt gerne ASCII Code drin. Da scheitere ich. Ich habe keine Ahnung, wie ich da jetzt irgendwas rein bekomme. Kann mir da jemand helfen?

    Auch wenn ich eher mit NASM als mit GAS arbeite, aber das war jetzt nicht schwer zu finden: https://cs.lmu.edu/~ray/notes/gasexamples/

    message: .ascii "Hello, world\n"
    

    ...oder .asciz für automatische Null-Terminierung.



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

    Bin gestern beim oberflächlichen Stöbern hierüber gestolpert, das könnte irgenwann beim raspi-experimentieren nützlich sein: https://elinux.org/RPi_Framebuffer. Das scheint mir ne grobe Anleitug zu sein, wie man mit "baremetal" auf dem Standard-Raspi nen Videomodus setzt und einen Framebuffer bekommt. Das liest sich allerdings so, als gäbe es für diesen Boradcom-Grafikchip keine öffentliche und freie Doku. Die Infos scheinen aus dem Quellcode des Linux-Grafiktreibers für den Chip zusammengeklaubt zu sein.

    Danke! Werde ich mir anschauen. Hab mir jetzt mal einen HDMI Capture bestellt, um zu sehen, was der Pi wirklich macht. qemu-aarch64 und qemu-system-aarch64 kriegen das Gerät anscheinend nicht so emuliert, wie es sein müsste. Habe ich zumindest mal den Eindruck.

    Mir reicht es schon, wenn ich tatsächlich Grafik ausgeben kann. Das muss kein 3D sein oder so, Hauptsache ich bringe was im Grafikmodus auf den Monitor. Wenn ich wirklich auf die Weise meinen Bordcomputer baue, soll es ja schön aussehen 😉

    Die Infos sind allerdings ziemlich mager - besonders im Vergleich zu dem, was z.B. für VGA-Grafik oder VBE auf dem PC öffentlich verfügbar ist. Vielleicht findest du ja irgendwo noch was besseres 😉

    Wird sich zeigen. Aber im Moment bin ich noch gar nicht an dem Punkt, überhaupt an Grafik zu denken ;). Da gibt es noch vieles, was ich noch nicht wirklich bei Assembler verstehe. Ich habe aber festgestellt GDB ist dein Freund ;). Den verwende ich seit gestern und sehe mittlerweile schon deutlich klarer, was da abgeht.

    Wenn du das zu Fuss machen willst dann könte das sehr mühselig werden. Da muss man wohl den Controllerchip, an dem das Speichergerät angeschlossen ist programmieren. Also USB, SATA oder was auf immer. Und selbst dann hast du noch kein wirkliches Dateisystem, sondern kannst nur auf Datenblöcke des Speichergeräts zugreifen. Da brauchts dann noch Code für ein Dateisystem... das sind Sachen, mit denen man sich viele Jahre befassen kann, wenn man Bock hat 😉

    Da gehe ich auch von aus. Aber, ich will ja kein Rad neu erfinden. Warten wir mal ab, was ich alles brauchen werde und bis wohin mein Interesse schlussendlich reicht. Anscheinend scheitere ich ja schon daran, eine simplte Textdatei zu öfnen ;).

    Man kann aber durchaus auch ohne Dateisystem. Daten kann man z.B. auch in seine Binary packen und zusammen mit dem Code laden, wenns nicht zu viel wird.

    Das ist gar keine blöde Idee muss ich sagen! Behalte ich mal im Hinterkopf.

    Ich hab nicht wirklich Ahnung von ARM64, aber das was ich beim groben Stöbern so gesehen habe, sieht mir so aus, als gäbe es da keine push/pop-Instuktionen. Das scheint tatsächlich so gemacht zu werden. Deinem Beispiel nach zu urteilen, bist du wohl auch auf diese Seite hier gestossen:

    Ich bin über ein paar Seiten gestolpert. Die war auch dabei ;).

    Da scheint ja schonmal einiges zum Stack erklärt zu werden, z.B. - was ich interessant fand - dass das die Adresse im SP-Register wohl immer 16-Byte-Alignment braucht, während man die Werte, die man auf den Stack schreibt, auch ein Offset relativ zu SP haben dürfen, dass dieses Alignment nicht unbedingt einhält. Das dürfte wohl mit einer der Gründe für dieses "Vorreservieren" von Stack sein.

    Ich bin mir noch nicht ganz sicher, ob man mit der aarch64 Methode gegenüber dem push/pop einen Vorteil hat. Es scheint aber technisch ja irgendwie flexibler zu sein. Ach wie schlau ich mich schon wieder anhöre als als etwas besserer Noob ;).

    Scheinbar ist das hier das Analog zu push und pop wie man es von x86 her kennt:

    str   x0, [sp, #-16]!    // push {x0}
    ldr   x0, [sp], #16      // pop {x0}
    

    So, oder auch nach meinem Beispiel, funktioniert es auf jeden Fall wunderbar. Genauso, wie ich es in x86 Tutorials gesehen habe. Verwende ich jetzt mal so, mal schauen wie sich das mit gestiegener Erfahrung dann verhält.

    Da wird SP immer um 16 Bytes verschoben. Das ist nicht gerade speichersparsam, wenn man nur einzelne Register auf den Stack schiebt. Wenn man will, kann man die sich sicher auch in ASM-Makros packen die man "push" und "pop" nennt... ist aber wohl nicht die beste Methode - eben wegen den Alignment-Anforderungen von SP. Der Artikel erwähnt aber auch, ein anderes Register als SP für den Stack Pointer zu nehmen (und ein paar Fallgruben, wenn man das tut). Das könnte vielleicht eine Alternative sein.

    An ein Makro hab ich auch schon gedacht. Aber halte ich für nicht notwendig. Man tippt zwar ein bisschen mehr, aber wenn man nicht viel tippen will, ist man in meinen Augen bei Assembler ohnehin völlig falsch ;). Mit anderen Methoden setze ich mich auseinander, wenn ich Bedarf habe. Noch reicht das alles für mein Vorhaben.

    Auch wenn ich eher mit NASM als mit GAS arbeite, aber das war jetzt nicht schwer zu finden: https://cs.lmu.edu/~ray/notes/gasexamples/

    message: .ascii "Hello, world\n"
    

    ...oder .asciz für automatische Null-Terminierung.

    Wie man eine ASCII-Zeichenkette mit einem Label belegt, ist mir soweit klar. Hallo Welt Programme hab ich schon hinbekommen. Meine Frage ist aber eher, wie reserviere ich denn Speicher, um aus einer geöffneten Datei einen Text zu lesen? Also keinen statischen Text zu haben, sondern eben einen variablen. Da komme ich noch nicht ganz mit.



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

    Mir reicht es schon, wenn ich tatsächlich Grafik ausgeben kann. Das muss kein 3D sein oder so, Hauptsache ich bringe was im Grafikmodus auf den Monitor. Wenn ich wirklich auf die Weise meinen Bordcomputer baue, soll es ja schön aussehen 😉

    Ja, die 3D-Funktionen des Chips wären bei der lausigen Dokumentation auch eine echte Herausforderung. Da nimmt man vermutlich doch besser Linux und den GLES-Treiber den es da denke ich mal für den Raspi gibt.

    Ein paar Beschleuniger-Funktionalitäten wie Zugriff auf einen Blitter oder für Alpha-Blending wären allerdings schon ganz nützlich. Diese Dinge stehen einem aber auch auf dem PC nicht unbedingt zur Verfügung, da diese Funktionen etwa zeitgleich mit dem ersten DirectX als VBE-Erweiterungen in den Vesa-Standard übernommen wurden und sich daher in dieser Form nie sonderlich breit durchgsetzt haben. Letztlich ist VBE auch nur eine simple Grafik-API mit der man sich eben spart, jede einzelne Grafikkarte direkt programmieren zu müssen. Das ist schon eine Abstraktionsebene über der im Link besprochenen Lowlevel-Grafik für den Raspi.

    Wird sich zeigen. Aber im Moment bin ich noch gar nicht an dem Punkt, überhaupt an Grafik zu denken ;). Da gibt es noch vieles, was ich noch nicht wirklich bei Assembler verstehe. Ich habe aber festgestellt GDB ist dein Freund ;). Den verwende ich seit gestern und sehe mittlerweile schon deutlich klarer, was da abgeht.

    Ich würde auch sagen, dass alles in Assembler zu machen, für ein größeres Projekt wie das wo du da hin willst, auch nicht unbedingt die richtige Wahl ist. Wenn ich sowas umsetzen wollte, dann würde ich mich auf diejenigen Dinge beschränken, die benötigt werden um ein in einer höheren Programmiersprache geschriebenes Programm ausführen zu können. Immer noch "baremetal" wohlgemerkt.

    Das dürfte vor allem erstmal die initialisierung einer Ausführungsumgebung sein. Ich mache in meiner Freizeit gerade etwas ähnliches für PC+DOS, und auch wenn das da sehr anders laufen dürfte, sollte es doch einige Parallelen geben. Das beinhaltet z.B. solche Dinge wie das initialisieren des .bss-Section mit Nullen oder die Speicherdetektion (RAM). Wie viel Speicher gibt es und welche (physischen) Speicherbereiche können von dem Programm überhaupt genutzt werden? Es gibt da ja schliesslich diverse Bereiche des Addressraums, auf die Hardware gemappt ist. Die sollte man tunlichst nicht als regulären Arbeitsspeicher nutzen. Z.B. ein C-Array für reguläre Programmdaten, das durch einen dummen Zufall über ein paar Hardware-Register läuft, wird das Program wahrscheinlich lustige Dinge tun lassen und kann im schlimmsten Fall sogar die Hardware beschädigen. Vielleicht ist das auf dem Raspi ja eindeutig spezifiziert, welche Speicherbereiche das Programm nutzen darf, und welche nicht - da kann man das dann einfach hartcodieren. Auf em PC muss man dafür erstmal das BIOS fragen und bekommt je nach installierter Hardware und BIOS teilweise sehr unterschiedliche Antworten.

    Dann muss die CPU auch in einen Modus versetzt werden, die an für die Ausführung des Programms haben will oder benötigt. Auf dem Raspi könnte das bereits direkt nach dem Booten schon der Fall sein. Unter DOS ist das allerdings ein bisschen umfangreicher, da sich die CPU unter DOS im 16-Bit Real Mode befindet und erstmal in den 32-Bit Protected Mode versetzt werden muss, damit sie überhaupt Code, der mit einem modernen C- oder C++-Compiler gebaut wurde, ausführen kann. Dazu gehört bei mir auch noch das Aufsetzen eines Stack, Speicherreservierung für das eigentliche Programm und das Laden des 32-Bit-Codes. Bei meinem Hobbyprojekt ist der Assembler-Code hauptsächlich ein Loader in 16-Bit-Code, der die Ausführungsumgebung für das Programm aufsetzt und dann die eigene Executable via Dateisystem-Funktionen öffnet, den 32-Bit-Teil des Programms lädt und dann dort hineinspringt. Auf diese Weise kann die Executable sogar größer sein, als es die Limitierungen des 640 KiB DOS-Speichers eigentlich zulassen würden.

    Die Idee ist, erstmal eine simple C-Standarbibliothek ans laufen zu bekommen. Man braucht zumindest irgendein malloc und noch ein paar andere Dinge, wenn man halbwegs sinnvoll in einer höheren Programmiersprache entwickeln will. Dafür bietet sich bei solchen Baremetal-Projekten newlib an, die gerne im Embedded-Bereich eingesetzt wird. Diese Implementierung der C-Standardbibliothek (libc) erfordert lediglich, dass man eine Handvoll Systemfunktionen implementiert, den Rest der libc stellt Newlib dann auf Basis dieser Systemfunktionen zur Verfügung. Für viele Systemfunktionen reicht allerdings eine triviale Minimal-implementierung, die lediglich eine Fehlermeldung zurückgibt. Das wichtigste dürfte hier wohl die sbrk-Funktion sein, mit der Newlib den Prozessspeicher für dein Proramm erweitern kann. Das ist die grundlegende Speicherreservierungs-Funktion, auf der das Newlib-malloc dann aufsetzt.

    Für mein DOS-Projekt muss ich allerdings schauen, ob ich nicht so etwas änliches wie mmap implementiere und Newlib irgendwie verklickern kann stattdessen das zu nutzen. Mit sbrk kann man nämlich nur einen zusammenhängenden Speicherbereich erweitern. Da ich keinen virtuellen Speicher verwende, ist das nicht unbedingt der beste Ansatz, da der physische Speicher wegen der oben genannten reservierten Speicherbereiche fragmentiert ist, und ich mit so einer Methode entweder den Speicher nicht vollständig nutzen könnte, oder eben Gefahr laufe, dass mir ein malloc dann irgendwann einen Speicherbereich zurückgibt, auf den irgendeine Hardware gemappt ist (nicht gut, s.o.).

    Wenn eine libc läuft, dann könntest du schonmal immerhin sinnvoll in C weiterprogrammieren. Das dürfte für so ein Projekt auf jeden Fall produktiver sein, als durchgehend alles in Assembler zu machen. Mit einer funktionierenden libc ist es dann übrigens auch kein großer Schritt mehr, z.B. die libstdc++ zu bauen (GCC sollte das mit Newlib unterstützen) - wenn wohl auch erstmal ohne Dinge, die weiteren OS-Support benötigen wie std::thread und sowas. Dann könntest du dein Baremetal-Programm auch in C++ weiter schreiben. Zumindest bei meinem Hobbyprojekt ist das das Ziel, wo ich erstmal hin möchte. Einen funktionierenden C++20-Compiler mit Standardbibliothek, der mir funktionierende DOS-Programme ausspuckt 😉

    Da gehe ich auch von aus. Aber, ich will ja kein Rad neu erfinden. Warten wir mal ab, was ich alles brauchen werde und bis wohin mein Interesse schlussendlich reicht. Anscheinend scheitere ich ja schon daran, eine simplte Textdatei zu öfnen ;).

    "Textdatei öffnen" ist halt so ne Sache, wenn man noch kein Dateisystem zur Verfügung hat. Wie gesagt, die Daten wie z.B. die Textdatei in die Binary einbauen, so dass du dann vom Linker ein Symbol für die Text-Daten zur Verfügung gestellt bekommst. Das ist dann in deinem Programm letztendlich eine Speicheradresse, an der die Daten zu finden sind. Der Text wird dann zusammen mit deinem Programmcode geladen. Hier werden ein paar Methoden gezeigt, wie man sowas machen kann.

    An ein Makro hab ich auch schon gedacht. Aber halte ich für nicht notwendig. Man tippt zwar ein bisschen mehr, aber wenn man nicht viel tippen will, ist man in meinen Augen bei Assembler ohnehin völlig falsch ;). Mit anderen Methoden setze ich mich auseinander, wenn ich Bedarf habe. Noch reicht das alles für mein Vorhaben.

    Ich sähe in dem Makro eher den Vorteil der besseren Lesbarkeit. Bei der Instruktion muss man immer kurz überlegen, was da gerade gemacht wird. Das könnte ja auch irgendein anderes "store" sein, das nichts mit dem Stack zu tun hat.

    Wie man eine ASCII-Zeichenkette mit einem Label belegt, ist mir soweit klar. Hallo Welt Programme hab ich schon hinbekommen. Meine Frage ist aber eher, wie reserviere ich denn Speicher, um aus einer geöffneten Datei einen Text zu lesen? Also keinen statischen Text zu haben, sondern eben einen variablen. Da komme ich noch nicht ganz mit.

    Nun, den Speicher hast du ja bereits mit test: .zero .byte 16 reserviert. Allerdings wird dieser direkt in deine Binary eingebaut, als eine Folge von 16 0-Bytes. Wenn du nur den Speicher benötigst, ohne irgenwelche festen Daten, dann ist das eher ein Fall für die .bss-Section und nicht die .data-Section. In ersterer wird nur Speicher reserviert und diese Sektion wird für gewöhnlich auch nicht in die Binary geschreiben. Lediglich Referenzen auf diesen werden vom Linker aufgelöst.

    Wenn du allerdings eine "Textdatei laden" willst und kein Dateisystem hast, dann ist das allerdings doch wieder ein Kandidat für die .data-Section. Schau dir nochmal den Link an, den ich etwas weiter oben gepostet habe, da wird grob angerissen, wie man eine Datei in die Binary einbettet. Die landet dann, wenn man es richtig macht, in der .data-Section so als hätte man die einzelnen Bytes der Datei im Assembler alle manuell eingetippt: https://csl.name/post/embedding-binary-data/



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

    Dann muss die CPU auch in einen Modus versetzt werden, die an für die Ausführung des Programms haben will oder benötigt. Auf dem Raspi könnte das bereits direkt nach dem Booten schon der Fall sein. Unter DOS ist das allerdings ein bisschen umfangreicher, da sich die CPU unter DOS im 16-Bit Real Mode befindet und erstmal in den 32-Bit Protected Mode versetzt werden muss, damit sie
    überhaupt Code, der mit einem modernen C- oder C++-Compiler gebaut wurde, ausführen kann.

    Das sind x86 Besonderheiten, die man bei anderen CPUs nicht findet. Üblicherweise gibt es da nur einen Supervisor- und einen Usermode. Was noch von Interesse ist, was für eine Firmware auf dem System ist, und wie diese das System initialisiert. UEFI ist bei PCs mittlerweile auch umfangreicher, aber früher war BIOS sehr einfach und kein Vergleich z.B. zu OpenFirmware die es bei PowerMacs, SUNs und IBMs Power Series gab/gibt.

    x86 war, ist und wird wahrscheinlich solange diese Plattform lebt immer Gefrickel bleiben. Wie schön könnte die Welt doch sein, wenn IBM statt des 8088 einen 68000 genommen hätte.

    Die Idee ist, erstmal eine simple C-Standarbibliothek ans laufen zu bekommen.

    Ich würde damit meine Lebenszeit nicht verschwenden. Entweder nimmt man einen Microcontroller z.B. Raspberry Pi Pico (oder einen Arduino) und steuert dann via SPI ein Display an (dafür gibt es fertige Display Lösungen), oder man bootet den großen Pi direkt in Linux und programmiert das Teil ganz normale als Linux System.



  • @john-0 sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

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

    Dann muss die CPU auch in einen Modus versetzt werden, die an für die Ausführung des Programms haben will oder benötigt. Auf dem Raspi könnte das bereits direkt nach dem Booten schon der Fall sein. Unter DOS ist das allerdings ein bisschen umfangreicher, da sich die CPU unter DOS im 16-Bit Real Mode befindet und erstmal in den 32-Bit Protected Mode versetzt werden muss, damit sie
    überhaupt Code, der mit einem modernen C- oder C++-Compiler gebaut wurde, ausführen kann.

    Das sind x86 Besonderheiten, die man bei anderen CPUs nicht findet. Üblicherweise gibt es da nur einen Supervisor- und einen Usermode.

    Schon klar. Ich kann aber nur aus PC-Erfahrung sprechen, daher auch "Auf dem Raspi könnte das bereits direkt nach dem Booten schon der Fall sein".

    Es ist aber schon faszinierend, dass selbst moderne x86_64-CPUs noch immer erstmal im 16-Bit Real Mode starten. Ich kann mir schon denken, dass es da nicht viele Systeme gibt, die etwas vergleichbares machen. Geschweige denn überhaupt noch sowas wie einen über 30 Jahre alten 16-Bit-Modus unterstützen 😉

    Was noch von Interesse ist, was für eine Firmware auf dem System ist, und wie diese das System initialisiert. UEFI ist bei PCs mittlerweile auch umfangreicher, aber früher war BIOS sehr einfach und kein Vergleich z.B. zu OpenFirmware die es bei PowerMacs, SUNs und IBMs Power Series gab/gibt.

    Ja, wenn ich hier BIOS schreibe, dann meine ich damit eigentlich alles, was man spezifischer als BIOS, UEFI oder Firmware bezeichnen würde.

    x86 war, ist und wird wahrscheinlich solange diese Plattform lebt immer Gefrickel bleiben. Wie schön könnte die Welt doch sein, wenn IBM statt des 8088 einen 68000 genommen hätte.

    Die Idee ist, erstmal eine simple C-Standarbibliothek ans laufen zu bekommen.

    Ich würde damit meine Lebenszeit nicht verschwenden. Entweder nimmt man einen Microcontroller z.B. Raspberry Pi Pico (oder einen Arduino) und steuert dann via SPI ein Display an (dafür gibt es fertige Display Lösungen), oder man bootet den großen Pi direkt in Linux und programmiert das Teil ganz normale als Linux System.

    Das ist gerade ein wenig, als sagtest du einem Modelleisenbahnbauer dass er mit solchen unproduktiven Dingen seine Lebenszeit nicht verschwenden soll. Wenns Spass macht, warum nicht? Ausserdem lernt man dabei einiges über so ein System das vielleicht nochmal nützlich werden kann.

    Abgesehen davon erachte ich das auch jetzt nicht also so aufwendig, die Newlib ans laufen zu bekommen, wenn man den ganzen Rest drumherum mit Linker-Skripten und der Initialisierung schon gemacht hat. Um den kommt man ohnehin nicht herum - es sei denn man verzichtet komplett auf den Baremetal-Ansatz. Für die Newlib reicht es im Prinzip sbrk zu implementieren und vielleicht noch open, read, write und close ausschliesslich für die stdout/stderr Handles, also ohne Dateisystem und sowas - wenn denn printf/std::cout funktionieren sollen.



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

    Ja, die 3D-Funktionen des Chips wären bei der lausigen Dokumentation auch eine echte Herausforderung. Da nimmt man vermutlich doch besser Linux und den GLES-Treiber den es da denke ich mal für den Raspi gibt.

    Wie gesagt, gross was mit 3D würde ich gar nicht benötigen. Aber wie du ja selbst sagst, die Dokumentation ist mehr dürftig. Ich habe auf meinem Pi3-Server (Octoprint, Git, Postgres ...) Arch laufen. So an sich ist es deutlich angenehmer, meiner Meinung nach, als das Pi OS. Gerade auch wegen der aktuelleren Pakete und ich finde pacman auch besser als apt. Aber eben, so ist da in meinen Augen alles besser und er scheint auch schneller zu laufen, aber versuch da mal irgendwas mit 3D zu machen. Da ich das aber auf einem Server nicht brauche, kann ich drauf verzichten und deshalb hat die GPU auch nur das Minimum an Speicher.

    Ein paar Beschleuniger-Funktionalitäten wie Zugriff auf einen Blitter oder für Alpha-Blending wären allerdings schon ganz nützlich. Diese Dinge stehen einem aber auch auf dem PC nicht unbedingt zur Verfügung, da diese Funktionen etwa zeitgleich mit dem ersten DirectX als VBE-Erweiterungen in den Vesa-Standard übernommen wurden und sich daher in dieser Form nie sonderlich breit durchgsetzt haben. Letztlich ist VBE auch nur eine simple Grafik-API mit der man sich eben spart, jede einzelne Grafikkarte direkt programmieren zu müssen. Das ist schon eine Abstraktionsebene über der im Link besprochenen Lowlevel-Grafik für den Raspi.

    Nützlich wäre das mit Sicherheit!

    Ich würde auch sagen, dass alles in Assembler zu machen, für ein größeres Projekt wie das wo du da hin willst, auch nicht unbedingt die richtige Wahl ist. Wenn ich sowas umsetzen wollte, dann würde ich mich auf diejenigen Dinge beschränken, die benötigt werden um ein in einer höheren Programmiersprache geschriebenes Programm ausführen zu können. Immer noch "baremetal" wohlgemerkt.

    Ich beginne immer mehr dir da Recht zu geben. Wenn ich alleine bedenke, wie lange ich für ein beklopptes

    char test[11];

    strcpy(test, "Ja, geht!\n");

    jetzt brauche, da fängt die Lust langsam an zu bröckeln. Auf der anderen Seite macht es mir unglaublich viel Spass die Kontrolle über Register und den Speicher zu haben.

    Hier meine ich aber glaube ich, auch in C direkt auf Speicheradressen zugreifen zu können. Ich meine damals auf dem AmigaOS auf die Weise an die Bilder in meiner damaligen Digitalkamera gekommen zu sein. Muss ich mich mal wieder schlau machen, ist verdammt lange her und ich mache schon sehr lange nur C++.

    Das dürfte vor allem erstmal die initialisierung einer Ausführungsumgebung sein. Ich mache in meiner Freizeit gerade etwas ähnliches für PC+DOS, und auch wenn das da sehr anders laufen dürfte, sollte es doch einige Parallelen geben. Das beinhaltet z.B. solche Dinge wie das initialisieren des .bss-Section mit Nullen oder die Speicherdetektion (RAM). Wie viel Speicher gibt es und welche (physischen) Speicherbereiche können von dem Programm überhaupt genutzt werden? Es gibt da ja schliesslich diverse Bereiche des Addressraums, auf die Hardware gemappt ist. Die sollte man tunlichst nicht als regulären Arbeitsspeicher nutzen. Z.B. ein C-Array für reguläre Programmdaten, das durch einen dummen Zufall über ein paar Hardware-Register läuft, wird das Program wahrscheinlich lustige Dinge tun lassen und kann im schlimmsten Fall sogar die Hardware beschädigen. Vielleicht ist das auf dem Raspi ja eindeutig spezifiziert, welche Speicherbereiche das Programm nutzen darf, und welche nicht - da kann man das dann einfach hartcodieren. Auf em PC muss man dafür erstmal das BIOS fragen und bekommt je nach installierter Hardware und BIOS teilweise sehr unterschiedliche Antworten.

    Das ist ja mit ein Grund, warum ich das auf dem Pi machen will. Da hat man eine Hardware. Okay, es gibt Unterschiede zwischen den einzelnen Versionen, aber wenn ich etwas für den Pi3 machen will, dann ist da eben diese eine spezifische Hardware verbaut. Da wird die Hardware immer an die gleiche Stelle gemapt usw. Das ist auch, soweit ich es in den Datenblätter bislang sehen konnte, auch gut dokumentiert.

    Was ich eben immer noch verdammt spannend finde, wie ich eingangs ja den Youtuber genannt habe, in C lädt man diesen Header, öffnet diese Library und dann Magic, Text auf dem Bildschirm. Während man eben in Assembler direkt sagen kann

    mov adresse, irgendwas

    und wenn die Adresse die obere linke Ecke vom Bildschirm ist, hat man dort einen Punkt, ein Zeichen, oder wie auch immer. Richtig die Hardware bei der Arbeit beobachten. Für mich derzeit richtig grosses Kino! Jetzt nicht so wie der das gemacht hat, sondern allgemein gesprochen. Ich will das an Adresse X der Wert Y steht und peng, ist so. Ist ja bei C nicht so. wenn ich da sage:

    int x;

    x = 10;

    dann weiss ich zwar das die Variable X den Wert 10 hat, aber wo das jetzt im Speicher steht , keine Ahnung. Klar, ich könnte einen Zeiger drauf setzen und so dann schauen, wo es sitzt, aber irgendwie. Na ja. Wahrscheinlich ist meine Begeisterung auch gerade völlig irrational, aber sie ist eben da ;).

    Dann muss die CPU auch in einen Modus versetzt werden, die an für die Ausführung des Programms haben will ...

    Ja, da ist auch so was beim Pi. Der muss in El1 versetzt werden, wenn ich mich nicht irre. Das wäre Firmware. Normal ist er in El3, wenn ich da nicht ganz irre.

    Die Idee ist, erstmal eine simple C-Standarbibliothek ans laufen zu bekommen. ...

    Tatsächlich wird das wahrscheinlich in Kürze mein nächster Weg sein. Wenn ich noch weiter so hier dran hänge, ohne wirkliche Fortschritte zu machen, wird der Kurs gewechselt. Es ist unglaublich frustrierend, wenn man einfach keine Fortschritte macht.

    ... eben Gefahr laufe, dass mir ein malloc dann irgendwann einen Speicherbereich zurückgibt, auf den irgendeine Hardware gemappt ist (nicht gut, s.o.).

    Ich habe das im Moment eigentlich so verstanden, dass malloc() nichts anderes macht als das, was ich da in meinem Beispiel gezeigt habe. Also

    char *test;

    test = (char 😉 malloc(sizeof(char), 16);


    test: .byte 16

    Oder irre ich mich da jetzt? Denn in beiden Fällen wird ja Speicher reserviert, ich weiss aber nicht wo im Speicher.

    Wenn eine libc läuft, dann könntest du schonmal immerhin sinnvoll in C weiterprogrammieren. Das dürfte für so ein Projekt auf jeden Fall produktiver sein, als durchgehend alles in Assembler zu machen.

    Ich finde immer weniger Argumente, um dir zu widersprechen 😉

    "Textdatei öffnen" ist halt so ne Sache, wenn man noch kein Dateisystem zur Verfügung hat. Wie gesagt, die Daten wie z.B. die Textdatei in die Binary einbauen, so dass du dann vom Linker ein Symbol für die Text-Daten zur Verfügung gestellt bekommst. Das ist dann in deinem Programm letztendlich eine Speicheradresse, an der die Daten zu finden sind. Der Text wird dann zusammen mit deinem Programmcode geladen. Hier werden ein paar Methoden gezeigt, wie man sowas machen kann.

    Fängt an mir zu gefallen ;).

    Nun, den Speicher hast du ja bereits mit test: .zero .byte 16 reserviert. Allerdings wird dieser direkt in deine Binary eingebaut, als eine Folge von 16 0-Bytes. Wenn du nur den Speicher benötigst, ohne irgenwelche festen Daten, dann ist das eher ein Fall für die .bss-Section und nicht die .data-Section. In ersterer wird nur Speicher reserviert und diese Sektion wird für gewöhnlich auch nicht in die Binary geschreiben. Lediglich Referenzen auf diesen werden vom Linker aufgelöst.

    Ach ja .bss, da war ja was :(. Ganz übersehen.



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

    Ich würde damit meine Lebenszeit nicht verschwenden. Entweder nimmt man einen Microcontroller z.B. Raspberry Pi Pico (oder einen Arduino) und steuert dann via SPI ein Display an (dafür gibt es fertige Display Lösungen), oder man bootet den großen Pi direkt in Linux und programmiert das Teil ganz normale als Linux System.

    Das ist gerade ein wenig, als sagtest du einem Modelleisenbahnbauer dass er mit solchen unproduktiven Dingen seine Lebenszeit nicht verschwenden soll. Wenns Spass macht, warum nicht? Ausserdem lernt man dabei einiges über so ein System das vielleicht nochmal nützlich werden kann.

    Wenn man so etwas machen will, dann sollte man sich zumindest eine Hardware aussuchen die auch entsprechend dokumentiert ist. Wir reden hier nicht über einen Nachbau eines C64 sondern über einen 64Bit ARM Prozessor mit OpenGL ES fähigem 3D Chip.



  • @john-0 sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

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

    Ich würde damit meine Lebenszeit nicht verschwenden. Entweder nimmt man einen Microcontroller z.B. Raspberry Pi Pico (oder einen Arduino) und steuert dann via SPI ein Display an (dafür gibt es fertige Display Lösungen), oder man bootet den großen Pi direkt in Linux und programmiert das Teil ganz normale als Linux System.

    Da müsste man dann mal definieren, was eine Verschwendung überhaupt ist. Ich sehe in dem erlernen neuer Fähigkeiten, im der Verfolgen eines für sich erstrebenswerten Ziels keine Zeitverschwendung, auch wenn es am Ende vielleicht nicht produktiv ist. Ich habe schon viel mit Arduinos rum gespielt und tue das auch immer noch. So einen will ich auch mal in Assembler programmieren, einfach weil ich es mal getan haben will.

    Und wenn man es spannend findet, eben nicht direkt auf einem Linux zu programmieren? Weil man das dauernd macht? Wenn es Spass macht, sich einer neuen Herausforderung zu stellen?

    Schlussendlich ist deine Ansicht zu dem Thema genauso richtig und falsch wie meine. Ausserdem nützt sie mir beim gewinnen von Erfahrung eigentlich eher gar nichts und ist hier dann entsprechend auch etwas überflüssig.

    Wenn man so etwas machen will, dann sollte man sich zumindest eine Hardware aussuchen die auch entsprechend dokumentiert ist. Wir reden hier nicht über einen Nachbau eines C64 sondern über einen 64Bit ARM Prozessor mit OpenGL ES fähigem 3D Chip.

    Ähm, bist du zufällig auch in Foren unterwegs, wo Projekte für den Pi vorgestellt werden? Wo man eine 64Bit ARM Architektur mit OpenGL ES fähigem 3D Chip als Wetterstation oder Internetradio verwendet?

    Oder anders gefragt, soll ich mir jetzt irgendwo einen C64 kaufen, oder was in der Art, weil die Hardware da einfacher ist? Mich aber unter Umständen super viel Geld kostet? Anstatt einen Pi zu nehmen, den ich eh schon habe? Oder gleich alles in Chip8 umsetzen?

    Es gibt einen Spruch eine US-Präsidenten, den ich gut finde. Wir haben uns nicht dazu entschlossen es zu tun, weil es leicht ist, sondern wir haben uns dazu entschlossen, weil es schwer ist (kein Zitiat, aber dürfte klar sein was ich meine).

    Man wächst ja mit der Herausforderung, oder?



  • @john-0 sagte in [Raspberry Pi 3 (aarch64) in

    Wenn man so etwas machen will, dann sollte man sich zumindest eine Hardware aussuchen die auch entsprechend dokumentiert ist. Wir reden hier nicht über einen Nachbau eines C64 sondern über einen 64Bit ARM Prozessor mit OpenGL ES fähigem 3D Chip.

    Die Sachen um einen Videomodus zu setzen und einen Frambuffer zu bekommen sahen mir jetzt eher nicht ganz so wild aus. Das nutzt zwar nicht sämliche Fähigkeiten des Grafikchips, reicht aber schon aus um eine Menge Spass damit zu haben. Wenn du die Framebuffer-Speicheradresse hast, dann kannst du deine Pixel direkt in den Speicher schreiben. Und das kann durchaus nur eine Editor-Seite Assembler-Code sein, bis man an diesem Punkt angelangt ist. Zumindest auf dem PC bewegt sich die Codemenge in dieser Größenordnung.

    Die Felder virtual_width, virtual_height, x_offset und y_offset der Raspi-Framebuffer-Datenstruktur sehen mir auch so aus, als ob man damit eventuell sogar Double-Buffering und Soft-Scrolling hinbekommen könnte. Mir persönlich würd das zum "spielen" schonmal reichen. Damit geht schon einiges, selbst wenn man die CPU die meisten Kopier- und Blending-Operationen machen lässt, die eigentlich auch die GPU könnte. Das war schon damals auf nem ranzigen i386 flott genug, das sollte ein Raspi doch um einiges besser hinbekomen 😉

    Ausserdem will man sehr wahrscheinlich selbst bei erstklassiger Dokumentation des Grafikchips nicht wirklich jedes Feature der Hardware "zu Fuß" nutzen. Das wäre zumindest mir persönlich dann doch zu viel Zeug und zu komplex. Für anspruchsvollere 3D-Sachen würde ich dann auch in ein Linux booten und den GLES-Treiber nutzen.

    Edit: @john-0 wenn du allerdings eine besser dokumentierte Hardware als einen Raspi vorzuschlagen hast, die vergleichbare GPU-Fähigkeiten hat, wäre ich nicht abgeneigt. Es wäre schon ziemlich cool, zumindest die Möglichkeit zu haben, auch die komplexeren GPU-Funktionen direkt programmieren zu können. Vielleicht sind ja ein paar Dinge nicht so kompliziert. Der erwähnte Blitter und Alpha Blending in Hardware wären zumindest etwas, in das ich persönlich noch zusätzliche Arbeit reinstecken würde, das nutzbar zu machen.

    Und wenn als Alternative eh nur simplere Hardware gibt, mit der man auch nur maximal einen Framebuffer bekommt, dann kann man (zumindest zum experimentieren) IMHO auch gleich beim Raspi bleiben. Erst recht wenn man den eh schon rumliegen hat 😉



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

    Ich habe das im Moment eigentlich so verstanden, dass malloc() nichts anderes macht als das, was ich da in meinem Beispiel gezeigt habe. Also

    
    test = (char *) malloc(sizeof(char), 16);
    
    ---
    
    test: .byte 16
    

    Oder irre ich mich da jetzt? Denn in beiden Fällen wird ja Speicher reserviert, ich weiss aber nicht wo im Speicher.

    Nein, malloc funktioniert anders. Wenn du in Assembler wie oben die 16 bytes reservierst, dann werden die 16 Bytes an der Stelle, wo das Label steht, direkt in dein Binary eingebettet (wenn es die .data oder .text Section ist), oder in den Speicherbereich, in den dein Binary geladen wird. Wo genau ist letztendlich davon abhängig, wo der Linker, bzw. dein Linker-Skript die Sektion, in der sich der Speicherbereich befindet platziert. Der Speicherbereich befindet sich aber für gewöhnlich dann innerhalb der Sektion exakt an der Stelle, an den du ihn in deinem Assembler-Code hingeschrieben hast. Vereinfacht gesagt befinden sich die 16 Byte dann fest eincodiert "mitten zwischen deinem Code". Da lässt sich nachträglich auch nichts mehr feigeben oder die Größe ändern.

    malloc hingegen ist ein dynamischer Speichermanager. Es gibt ja schliesslich auch noch das free-Pendant. Dieser kann während der Laufzeit angeforderten Speicher in belieberiger Größe (im Gegensatz zur zur Link-Zeit fest vorgegebener Größe) reservieren, wieder freigeben und dann auch für spätere Anforderungen erneut nutzen.

    Dazu fordert die Malloc-Implementierung Speicher vom System an, den sogenannten Heap. Schau dir mal diese Grafik hier an, das ist in etwa wie der Programmspeicher bei simplen Systemen meistens organisiert ist. Der Stack wächst von oben (hohe Adressen) nach unten und der Heap von unten nach oben. Zwischen Heap und Stack befindet sich feier Speicher. Die sbrk()-Funktion, mit der mehr Heap-Speicher angefordert wird, macht im Prinzip nichts anderes, als den Pointer auf das Ende des Heaps zu erhöhen und ihn näher in Richtung des Stacks zu bewegen (allerings ist sbrk heutzutage schon eine etwas archaische Methode, unter Linux wird da eher mmap verwendet. Für so simple Baremetal-Programme tuts aber durchaus auch ein sbrk-Ansatz).

    Diesen Heap-Speicher verwaltet nun die Malloc-Implementierung und baut darin u.a. auch eigene Datenstrukturen auf, mit denen der Heap-Speicher möglichst effizient dynamisch verwaltet werden kann. Das können Listen mit freien Heap-Speicherbereichen sein, Baumstrukturen und ähnliches. Ziel ist es dabei, erstens die malloc-Anfragen möglichst schnell bedienen zu können und zweitens auch bei unterschiedlichsten malloc/free Anfragemustern, welche die Anwendung produziert, die Fragmentierung des Heap-Speichers möglichst niedrig zu halten. Du kannst dir sicher vorstellen, dass bei einer naiven Implementierung nach etlichen malloc/free unterschiedlichster Größe der Heap irgendwann aussähe wie ein Schweizer Käse, so dass Anforderungen von großen, zusammenhängenden Speicherbereichen nicht mehr bedient werden können. Malloc-Implementierungen versuchen das so gut wie möglich zu vermeiden. Dafür braucht es schon einiges an Logik und Datenstrukturen mit Metadaten. Das ist im Prinzip, was ein malloc ausmacht.



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

    Da müsste man dann mal definieren, was eine Verschwendung überhaupt ist. Ich sehe in dem erlernen neuer Fähigkeiten, im der Verfolgen eines für sich erstrebenswerten Ziels keine Zeitverschwendung, auch wenn es am Ende vielleicht nicht produktiv ist. Ich habe schon viel mit Arduinos rum gespielt und tue das auch immer noch. So einen will ich auch mal in Assembler programmieren, einfach weil ich es mal getan haben will.

    Die GPU der Raspberry Pis sind nicht öffentlich dokumentiert. D.h. wenn man damit was machen will läuft man Gefahr, dass man Reverse Engineering machen muss. Das ergibt wenig Sinn. Daher auch mein Hinweis doch lieber einen Pi Pico zu nehmen und ein Display über SPI anzusteuern, denn dafür gibt es öffentliche Dokumentationen.



  • 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.


Anmelden zum Antworten