Autor Thema: Externe Unterprogramme mit NASM  (Gelesen 22899 mal)

bscreator

  • Gast
Gespeichert
« Antwort #40 am: 24. April 2010, 19:58 »
Zitat
Erstens: Wie du ein Programm intern strukturierst und wie du ein Programm auf dem Datenträger ablegst, sind zwei komplett verschiedene Paar Schuhe. Die Overlay-Technik hat nichts, aber auch garnichts, mit der Ablage auf Platte zu tun.
Achso, klar, weil wenn ich ein Programm mit einer Größe von z.B. 2000 Byte mit RawWrite auf eine Diskette schreib, dann werden sowieso die Sektoren der Reihe nach(also 0,1,2) aufgefüllt und nicht irgendwie auf der Disk verteilt, stimmts ?

Zitat
Am einfachsten erzeugst du eine (auf 512 Byte) gepaddete flache Binärdatei
Das mit der gepaddeten flachen Binärdatei musst mir BITTE genauer erklären

Zitat
Und wenn du C-Code nicht in Assembler umwandeln kannst, wirst du später bei den komplizierteren Themen große Probleme kriegen. Selbst ist der Compiler...
Mit kompliziert meinst wohl PM, A20-Gate, Multitasking (was im RM eigentlich nicht geht). Klar, da ist dann C einfacher, aber jetzt will ichs eben noch in Assembler machen.

Vielen Dank für eure Hilfe,
bsc

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #41 am: 24. April 2010, 20:21 »
Zitat
Erstens: Wie du ein Programm intern strukturierst und wie du ein Programm auf dem Datenträger ablegst, sind zwei komplett verschiedene Paar Schuhe. Die Overlay-Technik hat nichts, aber auch garnichts, mit der Ablage auf Platte zu tun.
Achso, klar, weil wenn ich ein Programm mit einer Größe von z.B. 2000 Byte mit RawWrite auf eine Diskette schreib, dann werden sowieso die Sektoren der Reihe nach(also 0,1,2) aufgefüllt und nicht irgendwie auf der Disk verteilt, stimmts ?
Exakt.

Zitat
Am einfachsten erzeugst du eine (auf 512 Byte) gepaddete flache Binärdatei
Das mit der gepaddeten flachen Binärdatei musst mir BITTE genauer erklären
NASM erzeugt dir - wenn du ein flaches Binärformat einstellst - direkt den assemblierten Code in Maschinensprache. Die Datei ist also da zu Ende, wo der Code aufhört.

Endet die letzte Zeile im Quelltext aber mit
times 512 - ($ - $$) db 0so füllt NASM die Datei so lange mit Nullen auf, bis du bei einer Dateigröße von 512 Bytes angekommen bist. Das nennt man Zero-Padding. :)

Zitat
Und wenn du C-Code nicht in Assembler umwandeln kannst, wirst du später bei den komplizierteren Themen große Probleme kriegen. Selbst ist der Compiler...
Mit kompliziert meinst wohl PM, A20-Gate, Multitasking (was im RM eigentlich nicht geht). Klar, da ist dann C einfacher, aber jetzt will ichs eben noch in Assembler machen.
Nein, wenn du Assembler schreiben möchtest, aber dir aus einem C-Tutorial die benötigten Infos nicht holen kannst, wirst du bei komplizierteren Algorithmen große Probleme kriegen. Deine Beispiele sind sehr nah an der Hardware, da reicht eine technische Dokumentation eigentlich hin, aber z.B. eine GUI sollte man in einer Hochsprache zumindest formulieren (ob man es dann in Assembler implementiert, ist eine Frage der persönlichen Schmerzgrenze).

Im RM geht Multitasking ganz prima, nur Speicherschutz gibt es nicht und die Speicherverwaltung ist etwas unhandlich, sogar präemptives Multitasking geht (vgl. AmigaOS). Ob bei dem wenigen RAM das was bringt, ist die zweite Frage.

Gruß,
Svenska

bscreator

  • Gast
Gespeichert
« Antwort #42 am: 25. April 2010, 12:00 »
Zitat
Nein, wenn du Assembler schreiben möchtest, aber dir aus einem C-Tutorial die benötigten Infos nicht holen kannst, wirst du bei komplizierteren Algorithmen große Probleme kriegen. Deine Beispiele sind sehr nah an der Hardware, da reicht eine technische Dokumentation eigentlich hin, aber z.B. eine GUI sollte man in einer Hochsprache zumindest formulieren (ob man es dann in Assembler implementiert, ist eine Frage der persönlichen Schmerzgrenze).
Also wie gesagt, ich bleib im Real-Mode (16-Bit). Ich kann aber trotzdem meinen Kernel in C schreiben und muss dann die C-Funktionen wie z.B. printf in Assembler nachschreiben, oder?. Und dann weiter meine OS-Hilfsprogramme in C schreiben, oder?

Aber wie kann man eine GUI in C schreiben ? Ich kann mir das nur so vorstellen, dass ich dann jedes einzelne Farbbit setzen muss, aber dann müsst ich ja mehrere Millionen Bits setzen...bzw. jedes einzelne Bit im Videospeicher setzen muss...

Vielen Dank euch allen,
bsc
« Letzte Änderung: 25. April 2010, 12:04 von bscreator »

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #43 am: 25. April 2010, 14:35 »
Hallo,

Also wie gesagt, ich bleib im Real-Mode (16-Bit). Ich kann aber trotzdem meinen Kernel in C schreiben und muss dann die C-Funktionen wie z.B. printf in Assembler nachschreiben, oder?. Und dann weiter meine OS-Hilfsprogramme in C schreiben, oder?

Du kannst im Real Mode den Kernel in C (oder einer anderen Hochsprache) schreiben, brauchst allerdings einen Compiler, der dir dann auch 16-Bit-Code erzeugt. Der gcc tut das nicht.

Alle C-Funktionen (wie z.B. printf) musst du selbst programmieren, diese sind Teil der C-Standardbibliothek (libc). Wenn du eine vernünftig dokumentierte Schnittstelle zum System hast, kannst du deine Hilfsprogramme in der Sprache schreiben, in der du sie haben möchtest - also auch in Assembler. Windows- oder Linuxprogramme können auch in Assembler geschrieben sein...

Aber wie kann man eine GUI in C schreiben ? Ich kann mir das nur so vorstellen, dass ich dann jedes einzelne Farbbit setzen muss, aber dann müsst ich ja mehrere Millionen Bits setzen...bzw. jedes einzelne Bit im Videospeicher setzen muss...
Ich würde die Bitzugriffe auf Bytes zusammenführen (und im 24/32-Bit-Modus hast du pro Pixel ja 4 Bytes). Ist nicht besonders schnell, aber funktioniert. Für Beschleunigung brauchst du dann hardwarespezifische Treiber... was doof ist.

Gruß,
Svenska

bscreator

  • Gast
Gespeichert
« Antwort #44 am: 25. April 2010, 19:48 »
Zitat
Wenn du eine vernünftig dokumentierte Schnittstelle zum System hast, kannst du deine Hilfsprogramme in der Sprache schreiben, in der du sie haben möchtest
Meinst Du mit Schnittstelle das Betriebssystem oder das Dateisystem?

Gruss,
bsc

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #45 am: 25. April 2010, 23:33 »
Mit Schnittstelle meine ich das Ding zwischen Anwendung und Betriebssystem.

bscreator

  • Gast
Gespeichert
« Antwort #46 am: 05. May 2010, 12:12 »
Hallo,
hab gerade das NASM-Tutorial von Wikipedia mal ein bisl durchgelesen und ein paar Fragen dazu :

Angenommen ich hab zwei Dateien :
1. Der NASM-OS-Kernel (befindet sich in 0x10000)
2. Die Datei mit den vielen Funktionen (befindet sich in 0x40000)
Beide Dateien bzw. Programme befinden sich somit in unterschiedlichen Segmenten und die Funktion "putstr" befindet sich in der Funktionsdatei

Kann ich dann vom Kernel aus mit
call far putstr oder mit
call es:putstrdie Funktion "putstr" aufrufen ?

(wobei in beiden Versionen ES=4000)

Gruss,
bsc



Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #47 am: 05. May 2010, 12:54 »
Das setzt voraus, dass der Kernel-Code weiß, wo sich die Funktion "putstr" im Funktionssegment befindet. Normalerweise geht das nicht statisch, da der NASM zum Zeitpunkt des Kernel-Übersetzens ja nicht weiß, wo sich in einer anderen Datei die Einsprungadressen/Symboladressen befinden.

Du müsstest also eine Tabelle im Speicher haben, wo zu jedem Symbol (Funktion, Variable, ...), die du aufrufen möchtest, die entsprechende Speicheradresse steht, dann kannst du einen simplen FAR CALL dahin machen. (Diese Tabellen sind übrigens Teil der Reloziierung und damit in einem vernünftigen executable format [ELF] drin.)

Alternativ richtest du dich nach dem System, welches DOS und andere Betriebssysteme nutzen und verwendest für Systemcalls einfach Interrupts und einen Interrupthandler, der die Funktionen verteilt. Das liegt dann alles in einer Quelltextdatei und dort sind die Adressen ja bekannt. Als Handler eingetragen ist dir auch die Segmentadresse egal, fertig.

Im RM sind Interrupts auch billig. ;-) Solche Call-Geschichten machen sich mit einem linearem Adressraum billiger. ;-)

Gruß,
Svenska

bscreator

  • Gast
Gespeichert
« Antwort #48 am: 06. May 2010, 09:11 »
Hab mir doch gedacht, dass das nicht so einfach geht.

Also wenn die Funktion "putstr" z.B. bei 0x4000:0x0150 beginnt und ich diese aufrufen möchte, geht das nur über JMP 0x4000:0x0150 oder über JMP ES:BX
Zitat
Alternativ richtest du dich nach dem System, welches DOS und andere Betriebssysteme nutzen und verwendest für Systemcalls einfach Interrupts und einen Interrupthandler, der die Funktionen verteilt. Das liegt dann alles in einer Quelltextdatei und dort sind die Adressen ja bekannt. Als Handler eingetragen ist dir auch die Segmentadresse egal, fertig.
Du meinst, die entsprechenden Funktionen als eigenen Interrupt in die IVT eintragen?
Bei vielen Funktionen kann das aber ganz schnell zur Qual werden. :-o

Zitat
Das liegt dann alles in einer Quelltextdatei und dort sind die Adressen ja bekannt.
Sorry, das hab ich glaub nicht verstanden. Eine Datei in die IVT eintragen ?

Gruss,
bsc

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #49 am: 06. May 2010, 15:14 »
Hallo,

Ich weiß nicht, ob "JMP 0x4000:0x0150" geht, aber "JMP ES:0x0150" sollte klappen.

Softwareinterrupts hast du ja jede Menge frei, überlicherweise gibt es aber einen Funktionsverteiler (Allzweck-Interrupt). DOS nutzt den int 21h, was er konkret tun soll, steht in den Registern. Vergleiche hierzu auch den int 13h (Datenträger) oder int 10h (Video), die funktionieren ähnlich. Damit könntest du High-Level-Funktionen, wie Stringausgabe/Dateilesen erschlagen.

Innerhalb einer Quelltextdatei sind die Adressen aller Symbole bekannt (die muss NASM ja auflösen), daher hast du _dort_ auch Zugriff auf alle Adressen und kannst diese in deinen Interrupt-Funktionsverteiler eintragen. Der eigentliche Kern des Systems braucht die Adressen dann nicht zu wissen, sondern es reicht aus, den Interrupt korrekt aufzurufen.

Gruß,
Svenska

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #50 am: 06. May 2010, 15:37 »
Ich bin mir sicher, dass weder JMP ES:BX noch JMP ES:0x0150 das tun was ihr wollt/denkt. ES: ist ein segment override prefix, nicht Teil der Zieladresse.
Dieser Text wird unter jedem Beitrag angezeigt.

bscreator

  • Gast
Gespeichert
« Antwort #51 am: 06. May 2010, 17:47 »
Zitat
dass weder JMP ES:BX noch JMP ES:0x0150 das tun was ihr wollt/denkt.
Da muss ich Dir widersprechen. Ich habs schon mal ausprobiert und die Zielfunktion hat er korrekt aufgerufen.

Zitat
Innerhalb einer Quelltextdatei sind die Adressen aller Symbole bekannt (die muss NASM ja auflösen), daher hast du _dort_ auch Zugriff auf alle Adressen und kannst diese in deinen Interrupt-Funktionsverteiler eintragen.
Sorry, mit dem Begriff "Datei" komm ich nicht richtig klar. Meinst Du damit sämtliche Funktionen, die innerhalb der Datei deklariert sind, oder die Datei als solches ?


Zitat
daher hast du _dort_ auch Zugriff auf alle Adressen und kannst diese in deinen Interrupt-Funktionsverteiler eintragen
Mit Adressen meinst Du jetzt die Funktionsnamen (wie putstr) , oder ?
Interruptfunktionsverteiler  :? Meinst Du die IVT ?

Wenn ich so nen Allzweck-Interrupt "mache", dann muss ich doch trotzdem jede einzelne Funktion mit jeder einzelnen Codezeile in die IVT eintragen.

Es reicht nicht, eine Datei bereitzustellen und nur die Funktionsnamen in die IVT einzutragen, oder ?

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #52 am: 06. May 2010, 22:31 »
Mit "Datei" meine ich die ASM-Datei, in der du den Quellcode hast. Dort sind alle Symboladressen (also Adressen von Funktionen, Variablen, ...) bekannt.

Wenn du FUNKTION.ASM hast und du definierst dort Funktionen, dann weiß die Datei KERNEL.ASM nicht, wo diese Funktionen innerhalb der FUNKTION.ASM liegen. Das unter der Annahme, dass du beide Binärdateien (FUNKTION.BIN und KERNEL.BIN) in verschiedene Segmente lädst. Um eine solche Funktion aufzurufen, brauchst du also zwingend die absolute, vollständige Adresse der Funktion. Und die ist deinem Kernel nicht bekannt.

Daher nutzt man ja auch den Weg über SYSCALLs (wie die implementiert sind, ist egal); dort übergibt man in der Regel eine Funktionsnummer, die vorher definiert ist. Die Adresse der Funktion wird dann entweder aus einer Tabelle nachgeschlagen oder, wie im folgenden Beispiel, direkt ausgewertet.

Mit Adressen meine ich die Einsprungadressen in Funktionen (bzw. die Speicheradressen, wo man mit CALL oder JMP hinkann).

In der IVT registrierst du jeden Interrupt mit einem Handler (= eine Funktion). Da drin kannst du z.B. je nach Registerzustand die gewünschte Funktion unterscheiden. Die Funktionsnummer wird also in AX übergeben, eine eventuelle Unterfunktionsnummer in BX. Wie du das genau machst, bleibt natürlich dir überlassen - in jedem Fall sieht so deine SYSCALL-Schnittstelle aus.

Die Funktionsnummern bleiben aus Kompatiblitätsgründen konstant; die FreeBSD-Linux-Emulation basiert darauf, dass die Funktionsnummern in Linux-Binaries dynamisch durch die (meist kompatiblen) FreeBSD-Funktionsnummern ersetzt werden. Darum ist das keine Simulation, sondern ein "natives" ausführen.

Als Beispiel etwa so (formuliert in C):
void int21_handler(int ax, int bx, int cx, int dx)
{
  if( ax == 0 ) { ...putstr... };
  if( ax == 1 ) { ...readstr... };
  if( ax == 2 )  /* videofunktionen */
  {
    if( bx == 0 ) { ...setze videomodus... };
    if( bx == 1 ) { ...zeichne pixel... };
  }
}

Gruß,
Svenska

bscreator

  • Gast
Gespeichert
« Antwort #53 am: 07. May 2010, 09:35 »
Zitat
Wenn du FUNKTION.ASM hast und du definierst dort Funktionen, dann weiß die Datei KERNEL.ASM nicht, wo diese Funktionen innerhalb der FUNKTION.ASM liegen. Das unter der Annahme, dass du beide Binärdateien (FUNKTION.BIN und KERNEL.BIN) in verschiedene Segmente lädst. Um eine solche Funktion aufzurufen, brauchst du also zwingend die absolute, vollständige Adresse der Funktion. Und die ist deinem Kernel nicht bekannt.
Das ist mir schon alles klar

Also Du meinst, dass ich alle meine Funktionen (bzw. alle wichtigen Funktionen) als SysCalls, bzw. als Interrupts in die IVT eintragen sollte, oder ?

PS: Wieviel Interrupts bzw. SysCalls kann man eigentlich definieren (glaub 256) oder ?
PS2: Ein weiterer Vorteil wäre, dass ich dann keine Datei im Speicher halten müsste, also Speicherersparnis.

Gruß und danke,
bsc

RedEagle

  • Beiträge: 244
    • Profil anzeigen
    • RedEagle-OperatingSystem - Projekt
Gespeichert
« Antwort #54 am: 07. May 2010, 10:04 »
Ich habe das bei meinem bootloader so gelöst, dass die Interrupts 0xF0-0xFF je eine Funktion entsprechen - das erlaubt zwar max 15 Funktionen, aber bisher komme ich gut damit aus.
Kommt natürlich drauf an, wie ausgiebig die API sein soll.
Der Vorteil ist, dass man ein Register weniger "verbraucht" und man sich somit die calls besser in Code integrieren lassen.
So kann man diverse log-ausgaben erledigen, ohne auch nur einmal ein Register zu überschreiben (mal abgesehen von si)

Die Funktionen befinden sich alle in der Binärdatei des Bootloaders werden dort wie folgt in die IVT eigetragen:
        mov WORD [es:0xF0*4],   int_0xF0
        mov WORD [es:0xF0*4+2], cs
        mov WORD [es:0xF1*4],   int_0xF1
        mov WORD [es:0xF1*4+2], cs

die Funktion int_0xF0 kann dann z.B. so aussehen:
int_0xF0:
    call writestring ; si = string-offset; ds = segment des strings
    iret

Du musst in allen Funktionen die writestring aufruft natürlich beachten das ds und es idr auf "fremde" Segmente zeigen.

Der Bootloader kann nun andere Binärdateien laden, (in ein anderes Segment) und diese Programme können dann ganz einfach per int 0xF0 die writestring-Funktion des Bootloaders aufrufen


zu deinem 1. PS:
nahezu unendlich viele :)
du könntest ax:bx:cx:dx für eine fortlaufende Nummer benutzen, und die Argumente auf den Stack pushen :)

zu PS2:
Du musst die Datei im Speicher belassen, die die Funktionen beinhaltet. Wenn sich diese Funktionen so wie bei mir teil des Bootloaders sind ist das ohnehin nicht zu verhindern :)
Eine zusätzliche Datei (quasi ein shared object file) ist dann natürlich nicht mehr nötig.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #55 am: 07. May 2010, 14:23 »
Und ja, du solltest alle sinnvollen Funktionen als Syscalls definieren. In dem Moment kannst du darauf nämlich einfach und gut zugreifen. (Hilfsfunktionen sind keine Syscalls.)

Teile der Syscalls sind für die Anwendungen später relevant, andere Teile darf nur der Kernel nutzen. In einem speichergeschützem Betriebssystem kannst und solltest du das erzwingen; im RM ist das nicht vernünftig möglich.

bscreator

  • Gast
Gespeichert
« Antwort #56 am: 10. May 2010, 09:37 »
Zitat
Die Funktionen befinden sich alle in der Binärdatei des Bootloaders werden dort wie folgt in die IVT eigetragen
Hab ich das falsch verstanden, oder befinden sich die SW-Interrupts in der IVT und der Code für die SW-Interrupts in einem seperaten File im Speicher ?

Zitat
Hilfsfunktionen sind keine Syscalls.
Da geb ich Dir recht, dass man nicht jede einzelne kleine Abwandlung einer Funktion gleich als eigene Funktion in die IVT einträgt.

PS: Wenn man seine SW-Ints in die IVT einträgt, verbraucht man damit doch eigentlich fast keinen Arbeitsspeicher (bis auf den Teil, der für die neuen SW-Ints im Bereich 0x0000:0x0000 draufgeht, oder ?

Grüße,
bsc

RedEagle

  • Beiträge: 244
    • Profil anzeigen
    • RedEagle-OperatingSystem - Projekt
Gespeichert
« Antwort #57 am: 10. May 2010, 13:10 »
Zitat
Hab ich das falsch verstanden, oder befinden sich die SW-Interrupts in der IVT und der Code für die SW-Interrupts in einem seperaten File im Speicher ?
Die IVT beginnt an 0x0000:0x0000 ist hat höchst wahrscheinlich eine konstante Größe von 0xFF*(2+2)

Ein Eintrag in der IVT besteht aus dem Segment, und dem Offset einer Funktion.
=>Die Datei kann in irgendeinem Segment liegen. Du kannst auch die Datei für die Funktionen unter int 0xFF in ein anderes Segment laden als die für int 0xFE

Zitat
PS: Wenn man seine SW-Ints in die IVT einträgt, verbraucht man damit doch eigentlich fast keinen Arbeitsspeicher (bis auf den Teil, der für die neuen SW-Ints im Bereich 0x0000:0x0000 draufgeht, oder ?
Die IVT muss auf jeden fall vorhanden sein - und ist es auch. Du veränderst die Einträge später nur noch, fügst also nichts hinzu.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #58 am: 10. May 2010, 15:49 »
Im Speicher hast du keine Files. Du hast darin nur Daten, die unter einer bestimmten Adresse (aus Segment und Offset) erreichbar sind.

Wie du deine Quelltext strukturierst, damit du nicht eine riesige Assembler-Datei hast, bleibt ganz allein dir überlassen. Das hat an sich auch nichts mit der Strukturierung im Speicher zu tun, die sind unabhängig voneinander.

Du trägst auch nicht jede Funktion in die IVT ein, sondern nur eine generelle Routine, welche alle Funktionen zur Verfügung stellt. Diese Routine ist dann mittels eines Interrupts erreichbar.

In der IVT kannst du keinen Code eintragen. Dort schreibst du nur eine Adresse rein.

Gruß,
Svenska

bscreator

  • Gast
Gespeichert
« Antwort #59 am: 11. May 2010, 10:36 »
Zitat
Du trägst auch nicht jede Funktion in die IVT ein, sondern nur eine generelle Routine, welche alle Funktionen zur Verfügung stellt. Diese Routine ist dann mittels eines Interrupts erreichbar.
In der IVT kannst du keinen Code eintragen. Dort schreibst du nur eine Adresse rein.
Achso, also wird nur unsere Funktion in der IVT per Adresse "angemeldet", aber der Funktionscode für unsere angemeldete Funktion muss immer im Speicher verfügbar sein

Vielen Dank an euch,
bsc

 

Einloggen