Lowlevel
Lowlevel => Lowlevel-Coding => Thema gestartet von: bscreator am 19. April 2010, 19:00
-
Hallo,
ich wollt gestern meine 'putstr-Funktion' in eine seperate Datei (als externes Unterprogramm) schreiben und dann anderen Dateien freigeben:
Das externe Unterprogramm PROC.ASM:
global putstr
putstr:
lodsb
or al,al
jz putstr_end
mov ah,0x0E
mov bx,0x0007
int 0x10
jmp putstr
putstr_end:
retn
Das Hauptprogramm TK.ASM :
BITS 16 ;Set code generation to 16 bit mode
extern putstr
jmp start
msg_succ db 'successful',13,10,0
msg_welc db 'Welcome to RM-OS',13,10,0
start:
mov ax,0x0800
mov ds,ax
mov si,msg_succ
call putstr
mov si,msg_welc
call putstr
mov si,creboot
call putstr
retn
Das kompilieren der PROC.ASM funktioniert ohne Fehler.
Das kompilieren der TK.ASM bringt beim assemblieren den folgenden Fehler:
error: binary output format does not support external references
Dieser Fehler tritt bei den Zeilen call putstr
auf.
Habs schon mit jmp putstr versucht, ein FAR ans global angehängt, nichts funktioniert. Und das was im WIKI steht, über NASM-Unterprogramme funktioniert ebenfalls nicht.
Wisst ihr, wie man ein externes Unterprogramm (in NASM geschrieben) mit NASM aufruft ?
Vielen Dank,
bsc
-
Vermutlich solltest du einfach mal die Fehlermeldung lesen:
error: binary output format does not support external references
Mit flachen Binaries gehen externe Symbole irgendwie schlecht, weil in der Ausgabedatei keine Symbole landen. Wenn du das machen willst, brauchst du ein ordentliches Binärformat wie ELF.
-
Geht das dann mit NASM, bzw. unterstützt NASM dieses Binärformat ?
Ach: Ich hab ja gar keine Binärdatei daraus gemacht, habs ja nur assembliert. Wie kommt dann der auf binary output format ?
-
Geht das dann mit NASM, bzw. unterstützt NASM dieses Binärformat ?
Also ELF wird von NASM unterstützt.
-
Das schon, aber nur für 32-bit Code, soviel ich weiss.
Also kann ich gar keine externen Unterprogramme für mein 16-bit OS verwenden.
-
Du könntest "a.out", "obj" oder "coff" versuchen, aber ob das tut weiß ich nicht wirklich, siehe auch "nasm -hf" btw. :wink:
-
Bei mir war als Output-Format das COM-executable binary file - Format eingestellt. Deswegen hat er die Fehlermeldung gebracht. Mit dem Format "DOS 16 bit OMF object file" geht es, aber kann ich einen Bootloader auch in diesem Format schreiben?
Ist für einen Bootloader keine Binärdatei erforderlich (*.bin) ?
-
Für einen Bootsektor schon. Aber da sehe ich auch keinen Grund, das bisschen Code, das dort reinpasst, auf mehrere Dateien zu verteilen. Ansonsten könntest du immer noch erst ein richtiges Binärformat für die Objektdateien benutzen und erst dem Linker sagen, dass er daraus eine flache Binary basteln soll.
-
Aber da sehe ich auch keinen Grund, das bisschen Code, das dort reinpasst, auf mehrere Dateien zu verteilen.
Nene, ich will ja meine Kernelfunktionen auf mehrere Dateien verteilen.
Ansonsten könntest du immer noch erst ein richtiges Binärformat für die Objektdateien benutzen und erst dem Linker sagen, dass er daraus eine flache Binary basteln soll.
Ok, so ganz verstanden hab ich das nicht
Habs grad so versucht:
Aus dem Bootloader ne ganz normale Binärdatei gemacht (nasm -f bin -o ...)
Und anschließend das ganze zu nem IMG zusammengebunden.
copy boot.bin+tk.obj+proc.obj myos.img
Aber wenn ich das ganze dann ausführ, kommt leider nur das "Loading..." aus dem Bootloader. Kann das so eigentlich funktionieren ?
-
Naja, kommt auf deinen Bootloader an. Wenn der einen Linker eingebaut hat, geht das vielleicht schon, aber ansonsten eher nicht. ;)
Du musst die beiden Objektdateien zusammenlinken - in was für ein Format, hängt wiederum von deinem Bootloader ab. Ich vermute, dass es der typische notlösungsmäßige Bootloader ist, der eine flache Binary braucht. Aber das solltest du selbst am besten wissen. Wenn du es nicht weißt, wäre unsere Standardempfehlung einen Bootloader zu nehmen, von dem bekannt ist, dass er tut und dass er auch ein paar Sachen mehr unterstützt - in der Regel also GRUB.
-
Naja, kommt auf deinen Bootloader an. Wenn der einen Linker eingebaut hat, geht das vielleicht schon, aber ansonsten eher nicht.
Naja, ich verwend den "Standard-Bootloader" vom LowLevel-Magazin 1. Also hat der mit Sicherheit keinen Linker dabei. Also mein Bootloader verwendet das "normale" Binary-Format.
Du musst die beiden Objektdateien zusammenlinken - in was für ein Format, hängt wiederum von deinem Bootloader ab.
Ist es eventuell möglich,
1. die beiden Object-Dateien (TK.OBJ und PROC.OBJ) mit NASM zusammenzulinken (copy tk.obj+proc.obj myos.obj)
2. diese myos.obj dann mit einem anderen Linker in eine Binary umzuwandeln ?
3. Diese entstehende myos.bin dann wiederum mit der boot.bin zu verlinken?
Denn auf einen eigenen Bootloader will ich nicht verzichten...
Funktioniert das zusammenlinken eigentlich mit dem NASM-copy Befehl ?
-
1. die beiden Object-Dateien (TK.OBJ und PROC.OBJ) mit NASM zusammenzulinken (copy tk.obj+proc.obj myos.obj)
copy ist nicht nasm. Abgesehen davon geht es mit copy nicht. Du brauchst einen richtigen Linker (der .obj kann). Das kann ein crosscompilierter ld aus den binutils (macht keinen Spaß, va. nicht unter Windows, falls du doch ELF nimmst kann du allerdings Jidders Crosscompiler runterladen/verwenden, siehe Wiki) oder Microsoft Linker (aus Visual Studio oder was auch immer, da darfst du dann das Manual selbst lesen/verstehen). Wahrscheinlich gibt es auch mehrere kleinere Linker aus der osdev-Community, z.B. irgendwas names jloc von John fine (http://geezer.osdevbrasil.net/johnfine/index.htm) (uralt, schlecht gewartet, etc...).
2. diese myos.obj dann mit einem anderen Linker in eine Binary umzuwandeln ?
Der Linker ist dafür zuständig aus deinen *.obj eine Binary zu machen (und dabei Symbole aufzulösen).
-
Also kann ich mit Jidders Crosscompiler (oder JLOC) aus meinen (aus NASM erzeugten) OBJ-Dateien ELF-Binarys machen. Also boot.ELF, tk.ELF, PROC.ELF.
Und Jidders Crosscompiler bzw. JLOC machen dann gleichzeitig aus diesen 3 Files eine einzige Datei, die ich dann mit RawWrite auf ne Floppy schreiben kann, oder ?
-
In den OBJs sollten die Symbole ja drin stehen, also kannst du mit einem Linker aus diesen OBJs eine Binärdatei (ELF) erzeugen. Der Linker nimmt sich also die OBJs, du brauchst da keine einzelnen ELFs draus machen.
Diese kannst du später in eine flat binary umwandeln oder vom Linker direkt erzeugen lassen.
Wichtig ist, dass dein NASM als Ausgabe ein richtiges Object-File erzeugt.
-
Wichtig ist, dass dein NASM als Ausgabe ein richtiges Object-File erzeugt.
Und dass der benutzte Linker auch dieses Format unterstützt (und auch so kompiliert wurde, dass Format unterstützt wird).
-
Wichtig ist, dass dein NASM als Ausgabe ein richtiges Object-File erzeugt.
Und wie kann ich nachprüfen, ob mein NASM ein "richtiges" Object-File erzeugt ?
Und dass der benutzte Linker auch dieses Format unterstützt (und auch so kompiliert wurde, dass Format unterstützt wird).
Aber mit Jidders oder JLOC kann man doch ELF-Binarys erzeugen. Also unterstützen diese Linker doch dieses Format, oder meinst Du was anderes ?
-
Wichtig ist, dass dein NASM als Ausgabe ein richtiges Object-File erzeugt.
Und wie kann ich nachprüfen, ob mein NASM ein "richtiges" Object-File erzeugt ?
Indem du es mit -f <formatname> angibst?
Und dass der benutzte Linker auch dieses Format unterstützt (und auch so kompiliert wurde, dass Format unterstützt wird).
Aber mit Jidders oder JLOC kann man doch ELF-Binarys erzeugen. Also unterstützen diese Linker doch dieses Format, oder meinst Du was anderes ?
1. jloc kann kein ELF
2. Was ich damit meine ist, dass ld zB unglaublich viele Formate unterstützt, aber wenn man dann zB ld konkret für Linux kompiliert, bleiben nurnoch ein paar wenige übrig, weil er eben so konfiguriert ist, dass nur die Formate einkompiliert werden, mit denen das Host-OS was anfangen kann ("Wer braucht schon ELF unter Windows oder exe unter Linux?").
-
Achso, das sollt ich vielleicht noch hinzufügen :
Alle Programme, die ich zur Betriebssystementwicklung brauch (NASM, RawWrite, Hex-Editor, virtuelle Maschinen) laufen alle bei mir unter Windows.
Kennt jemand von euch die Befehle, mit denen man ELF oder "normale" Binarys aus Programmcode mit Jidders CC erstellen kann ?
-
Kennt jemand von euch die Befehle, mit denen man ELF oder "normale" Binarys aus Programmcode mit Jidders CC erstellen kann ?
In welcher Sprache soll denn der Sourcecode sein? Ich bin ehrlich gesagt etwas verwirrt. Auf was ich oben eigentlich verwiesen habe ist ld, dass Objektdateien (das Objektdateien bezieht sich nicht auf das Dateiformat ".obj", sondern darauf, dass sie sie kompilierten Code enthalten aber noch nicht gelinkt sind) zusammenlinkt (das heißt primäre Symbolereferenzen aus verschiedenen Objektdateien richtig auflösen). btw. du solltest dir mal anschauen wie das C/C++ Compile/Link-Modell aussieht, dann verstehst du vielleicht besser was wir mit Linken meinen (ich hatte nicht so das Gefühl, dass das angekommen ist)...
-
In welcher Sprache soll denn der Sourcecode sein?
Da das BIN-Format die entsprechende Syntax (zum Einbinden externer Unterprogramme) nicht kannte, hab ich das andere Format, genannt DOS 16 bit OMF object file als Zieldatei ausgewählt, das dann, zum assemblieren, funktioniert hat. Quellsprache NASM.
Auf was ich oben eigentlich verwiesen habe ist ld, dass Objektdateien zusammenlinkt
Also ist ld der entsprechende Jidders-CC-Befehl, oder ?
Etwas verwirrt bin ich schon, denn ich will ja keinen Code von Windows auf Linux oder andersherum portieren, sondern nur erreichen, dass ich mit NASM extere Unterprogramme (die auch in NASM geschrieben sind) durch Verwendung von GLOBAL und EXTERN einbinden kann.
-
Auf was ich oben eigentlich verwiesen habe ist ld, dass Objektdateien zusammenlinkt
Also ist ld der entsprechende Jidders-CC-Befehl, oder?
Wie wärs wenn du im Archiv nachsehen würdest, wie der "Befehl" heißt? :wink:
Etwas verwirrt bin ich schon, denn ich will ja keinen Code von Windows auf Linux oder andersherum portieren, sondern nur erreichen, dass ich mit NASM extere Unterprogramme (die auch in NASM geschrieben sind) durch Verwendung von GLOBAL und EXTERN einbinden kann.
Als Alternative kannst du auch die ganzen Assemblerdateien in einer Datei %includen, dann diese assemblieren und damit glücklich werden. Dann sparst du dir das Linken...
-
Als Alternative kannst du auch die ganzen Assemblerdateien in einer Datei %includen, dann diese assemblieren und damit glücklich werden. Dann sparst du dir das Linken...
Aber zwischen INCLUDE und externen Unterprogrammen gibt es einen großen Unterschied, aber den kennst Du mit Sicherheit.
Wie wärs wenn du im Archiv nachsehen würdest, wie der "Befehl" heißt?
Naja, gegoogelt hab ich bereits, aber da steht fast gar nichts über Jidders drin. Bis jetzt hab ich nur das Archiv nach C:\crosstools entpackt und die PATH-Variable gesetzt.
Da Du dich aber mit Sicherheit besser als ich mit Jidders auskennst, kannst mir doch bitte ein bisl unter die Arme greifen...
Auf jeden Fall bin ich für jede Hilfe eurerseits sehr dankbar
-
Jetzt wird ein GCC, den ich (Jidder) kompiliert habe, als "Jidders" bezeichnet... dabei weiß doch jeder, dass es mich nur einmal gibt ...
Der enthaltene Linker (und die restliche Software) ist ein 32-Bit Linker. Damit kannst du nichts anfangen.
Du kannst dir mal ALINK anschauen. Der ist laut Google als Ergänzung für NASM gedacht, und soll für 16-Bit Programme funktionieren. Ich bekomme ihn aber nicht zum laufen.
Ansonsten kannst du dir einen 16-Bit Linker von Borland oder Microsoft besorgen. Möglicherweise musst du dazu in den Paketen von Turbo C oder Visual C suchen.
-
Als Alternative kannst du auch die ganzen Assemblerdateien in einer Datei %includen, dann diese assemblieren und damit glücklich werden. Dann sparst du dir das Linken...
Aber zwischen INCLUDE und externen Unterprogrammen gibt es einen großen Unterschied, aber den kennst Du mit Sicherheit.
Nein, den kenne ich nicht.
-
Der enthaltene Linker (und die restliche Software) ist ein 32-Bit Linker. Damit kannst du nichts anfangen.
32-bit ? OK, dann werd ich den Jidder wieder ganz schnell vergessen.
Den von Borland hab ich mir auch schon runtergeladen und werd mal versuchen, ob es mit dem oder mit ALINK klappt.
Nein, den kenne ich nicht.
Auch wenn ich das eher für einen Witz von dir halte :
Mit Include werden die Codezeilen beim Linken in den Code mit eingefügt, was bedeutet, dass das Programm größer und größer wird. Dies ist besonders im Real-Mode wegen der Segmentgrenze (glaub 64k) ziemlich schlecht.
Externe Unterprogramme bekommen jedoch eigene Segmente, wodurch die Größe des Quellcodes im Prinzip nur vom Hauptspeicher begrenzt wird.
-
Mit Include werden die Codezeilen beim Linken in den Code mit eingefügt
Beim Assemblieren, nicht beim Linken.
was bedeutet, dass das Programm größer und größer wird. Dies ist besonders im Real-Mode wegen der Segmentgrenze (glaub 64k) ziemlich schlecht.
Externe Unterprogramme bekommen jedoch eigene Segmente, wodurch die Größe des Quellcodes im Prinzip nur vom Hauptspeicher begrenzt wird.
Das passiert sicher nicht automatisch. Dazu musst du den Code auch entsprechend laden, also vermutlich in deinem Bootloader. Und damit der Bootloader weiß, was er tun soll, braucht er vermutlich ein paar Informationen, wo er alles hinpacken soll. Ich glaube, mit flachen Binaries kommst du da nicht mehr sehr weit.
-
Das passiert sicher nicht automatisch. Dazu musst du den Code auch entsprechend laden, also vermutlich in deinem Bootloader. Und damit der Bootloader weiß, was er tun soll, braucht er vermutlich ein paar Informationen, wo er alles hinpacken soll.
Dafür bin ich dir sehr dankbar. Hab nämlich bis jetzt gedacht, dass das irgendwie automatisch passiert (weil es so in meinem ASM-Buch steht und ich mich stur darauf verlassen hab...).
Ich glaube, mit flachen Binaries kommst du da nicht mehr sehr weit.
Was meinst Du damit genau ?
-
Ich glaube, mit flachen Binaries kommst du da nicht mehr sehr weit.
Was meinst Du damit genau?
Naja, du möchtest, dass das ganze in verschiedene Segmente geladen wird, beim normalen Laden (also einfach über den BIOS-Interrupt ein paar Sektoren lesen) werden einfach die gelesenen Sektoren hintereinander in den Speicher gepackt, da wird in keinster Weise auf Segmentgrenzen geachtet. Daraus folgt, dass du irgendwie Informationen in deiner zu ladenden Datei haben musst, die dir sagen welcher Teil der Datei in welches Segment (also wohin in den Speicher) geladen werden muss. Bei ELF-Dateien gibt es dazu eine Programm-Header, die aus einer Liste von Segmenten (der Begriff Segment meint bei ELF nicht das gleiche wie im Real-Mode). Die Segmente bestehen dort jeweils aus einer physischen Basisadresse an die dieses Segment geladen werden soll, der Länge des Segments und das Offset innerhalb der Datei an dem der Code dieses Segments zu finden ist. Wenn man dann eine ELF-Datei laden will, dann wird man zuerst die Programm-Header laden (ganz am Anfang der Datei befindet die sich) und dort die ELF-Segmente durchgehen und jedes einzelne dann von dem Speichermedium laden und an die richtige Position im Arbeitsspeicher kopieren. Die Erklärung ist ist wahrscheinlich auch für andere Dateiformate in irgendeiner Weise gültig.
Der enthaltene Linker (und die restliche Software) ist ein 32-Bit Linker. Damit kannst du nichts anfangen.
Die Idee war eigentlich ELF-Objektdateien als Input zu verwenden und das ganze als Binärdatei ausgeben zu lassen, dass sollte doch eigentlich trotzdem funktionieren, oder nicht?
Noch als kleiner Denkanstoss, den du für dich selbst beantwortet musst: Willst du wirklich Realmode? Willst du wirklich so einen Bootloader schreiben?
-
Willst du wirklich Realmode? Willst du wirklich so einen Bootloader schreiben?
Schon viele haben versucht, mich in den Protected-Mode zu bringen und wahrscheinlich habt ihr auch alle Recht, aber ich bin eben ein Sturkopf, der will, dass sein OS auch auf einem 286er noch läuft.
-
286er können auch Protected Mode. Aber okay, 16-Bit-PM ist vermutlich fast noch obskurer. ;)
-
OK, dann eben auf nem 186
Geht das auch so :
1. Die 3 NASM-Quellcode-Files (Bootloader, Kernel und UP-File) assemblieren und mit nasm -f ... zu 3 richtigen OBJ-Dateien machen
2. Mit JLOC oder dem Borland TCC diese OBJ-Files zu 3 Binarys linken
3. Diese Binarys an unterschiedliche Sektoren der Floppy schreiben, damit man den Sektor mit den ext. UPs leicht in ein anderes Segment laden kann
-
2. Mit JLOC oder dem Borland TCC diese OBJ-Files zu 3 Binarys linken
Du musst zu einer Binary linken, sonst kann er beim Linken die Symbole nicht auflösen (es sei denn du willst auch noch shared-libraries - also DLL unter Windows bzw. .so unter Linux - im Bootloader haben, aber das ist zusätzlich nochmal sehr viel Aufwand).
-
Aber wenn ich diese zu einer Binary linken muss, dann kann ich diese auch nicht mehr in unterschiedliche Segmente laden, oder ?
Somit kann ich externe Unterprogramme vergessen, oder ?
-
Richtig, solche Unterprogramme kannst du vergessen.
Das ist mit flachen Binaries nicht möglich. Da du aber einen Bootloader programmierst, der höchstwahrscheinlich keine eigenen Executable-Formate definiert und laden kann, bist du auf flache Binärdateien beschränkt.
Was später geht - und auf 8-Bit-Maschinen verwendet wurde - sind sogenannte Overlays, also du teilst das Programm in mehrere 64K-Blöcke auf (die enthalten meist bisschen gemeinsamen Unterstützungscode) und wenn du eine Funktion aus einem anderen Overlay brauchst, dann "ersetzt" du dein Programm durch das Overlay - wenn die Funktion fertig ist, ersetzt das Overlay sich selbst wieder durch das Hauptprogramm.
Genau diese Technik kannst du weiternutzen, wobei du ja durch den größeren Adressraum netterweise mehrere dieser 64K-Overlays gleichzeitig im Speicher halten kannst. Dazu brauchst du eine Tabelle, die dir angibt, in welchem Overlay sich welche Funktion oder Variable befindet und die steht irgendwo im Dateiformat für Executables. Bei 32-Bit-ELF heißen die Overlays Segmente und die Tabellen stehen ELF-Header. Für PE/LE/a.out gilt entsprechendes.
Dein Bootloader muss aber eine flache Binärdatei sein, was dich aber nicht daran hindert, mehrere Overlays zusammenzulinken. Ohne "Betriebssystem" (Hilfscode) kann dein Bootloader aber nicht damit umgehen. Entweder, du beschränkst dich somit auf 64K oder aber du zerpflückst das Binary im Bootloader in die Bestandteile, schiebst die in verschiede Segmente und machst das zu Fuß.
Kurz: Für den Assembler kommt eine flat binary heraus, den Header und das Zerlegen in die Bestandteile und Overlays Segmente musst du selbst erledigen. Der Assembler hilft dir an der Stelle nicht.
Eventuell guckst du dir mal die Overlay-Technik der 8-Bitter an, der Real Mode des 8086 wurde stark davon inspiriert - auch, um 8080-CP/M-Programme günstig nach 8086 portieren zu können.
Allerdings können weder Minix noch Xenix auf 80286 (vermutl. auch 8086) Programme jenseits 64K Code + 64K Data verarbeiten. Die C-Compiler kamen mit Overlays/Segmentierung grundsätzlich nicht klar und die Technik wurde fallengelassen.
Gruß,
Svenska
-
Aber wenn ich diese zu einer Binary linken muss, dann kann ich diese auch nicht mehr in unterschiedliche Segmente laden, oder?
Dafür brauchst du ein Dateiformat, dass dein Bootloader natürlich lesen und sich danach richten muss, wie oben beschrieben.
Somit kann ich externe Unterprogramme vergessen, oder?
Du müsstest dazu erstmal definieren was du unter "externe Unterprogramme" verstehst, va. wie extern "extern" sein soll. Denn das Keyword "extern" von NASM sagt nur quasi "lieber Assembler, momentan [ = beim Assemblieren] weißt du nicht an welcher Adresse sich das Symbol befindet, aber beim Linken werde ich ganz bestimmt nachreichen wo es ist", d.h. es bezieht sich im Endeffekt direkt aufs Linken und zwar auf das Linken in eine Binary (wenn wir mal shared libraries außen vor lassen), da man die Adresse des Symbols ja "nachreichen" muss. Warum braucht der Assembler (wenn du direkt ins Binärformat assemblieren willst) oder der Linker beim Linken überhaupt die Adresse? Naja, die CPU hat keine Instruktion für call some_awesome_function_name, sondern nur für call <hier kommt die (absolute) Adresse für das Symbol some_awesome_function_name>.
Nun betrachten wir mal den Fall von shared libraries (mit irgendeiner frei gewählten Pseudonotation). Nehmen wir wie oben an, dass du ein Programm schreiben willst, dass call some_awesome_function_name aufrufen möchte und some_awesome_function_name ist in einer shared library (DLL oder was auch immer du dir da vorstellen möchtest, der Punkt ist hier, dass es zwei verschiedene Dateien sind). Ein Assembler (wenn man Binärformat als Ausgabeformat wählt) wird dir dann sagen "oh schade, es gibt aber keine Funktion some_awesome_function_name und ich bräuchte sie jetzt aber dringend, da ich die Adresse sofort eintragen muss", der Linker (wenn man ihm nicht sagt, dass er eine shared library benutzen soll) wird sagen "oh schade, irgendeine Objektdatei wollte some_awesome_function_name aufrufen, aber ich weiß nicht wo ich das finden könnte" und ein Linker dem man beim Linken auch die shared library übergeben hat (d.h. er weiß wo das Symbol später einmal zu finden sein wird) wird sagen "oh toll, irgendwoher krieg ich das Symbol und die Schmutzarbeit in der Callinstruktion die Adresse einzutragen muss dann irgendein Idiot zur Laufzeit machen" (der Idiot ist natürlich hier dann der Bootloader) und wird ungefähr sowas als Datei ausgeben
// Teil des Dateiformats
Benötigte shared libraries:
abc.dll
Relocation Table:
An Adresse 0x1234 muss noch die Adresse des Symbol "some_awesome_function_name" aus abc.dll eingetragen werden
// Der ausgegebene Code (der Maschinencode für call alleine soll hier 4Byte sein
// und direkt danach soll die Adresse an die gesprungen werden soll stehen)
0x1230 call 0
Zur Ladezeit deines Programms/Kernels muss dann irgendjemand (= Du/dein Bootloader) die abc.dll laden, dann die Relocation Table durchgehen und das Symbol "some_awesome_function_name" (Die Datei abc.dll muss dazu natürlich auch ein Dateiformat haben, dass eine Tabelle besitzt die Funktionsnamen auf Adressen abbildet, also hier some_awesome_function_name auf zB 0x6789) in der Datei abc.dll finden und die Adresse des Symbols nach 0x1234 schreiben.
Ich hoffe, dass macht klar, warum man dabei Dateiformate braucht.
-
Hallo,
@bscreator:
ich hab mal in einem FAT12/16/32-Bootsektor einen Loader für eine DOS-EXE untergebracht (für maximal die ersten 512kBytes), das relozieren (in dem Fall nur Segment-Nummern eintragen) hat natürlich nicht mehr rein gepasst. Das könnte man aber im ersten Code in der EXE selber erledigen (passende Parameterübergabe vom Bootsektor in die EXE vorausgesetzt). DOS-EXE-Dateien werden nicht so wie von bluecode (10:36) beschrieben verarbeitet sondern die werden einfach am Stück in den Speicher geladen und dann überarbeitet. Der Vorteil meiner Variante ist das man ein ganz normales Dateisystem verwenden kann (das ist mir persönlich sehr wichtig) und DOS-EXE-Dateien können von einigen älteren Tools erzeugt werden. Wenn man auf das relozieren der EXE verzichtet ist man natürlich an die 64kBytes gebunden aber das ist deutlich genug um darin einen Second-Stage-Bootloader unterzubringen der dann auch selber den PM für den Kernel vorbereitet. Ich hab damals mein eigentliches PM-Programm als Flat-Binary einfach an die EXE drangehangen und es zur Laufzeit mit seiner speziellen Signatur im Speicher gesucht, der DOS-EXE-Stub musste dann nur Speicher vorbereiten und den PM ordentlich aufsetzen.
Als Tool-Chain für den Assembler-Part habe ich den MASM 6 benutzt aber der letzte Borland C-Compiler liefert auch passende OBJ-Dateien welche der MS-Linker problemlos zu normalen DOS-EXE-Dateien verarbeitet.
Eine anderes Problem das ich sehe ist noch das jemand, der Object-Dateien per copy zusammenfummeln will, sich vielleicht erst mal mit der grundsätzlichen Arbeitsweise von Compiler/Assembler/Linker auseinandersetzen sollte. Vielleicht kann da mal jemand ein passendes Tutorial suchen und im Wiki-Anfänger-Artikel verlinken. Ich denke das man, bevor man mit der Entwicklung eines eigenen OS anfängt, mit der Tool-Chain per Du sein sollte.
Grüße
Erik
-
also du teilst das Programm in mehrere 64K-Blöcke auf
Damit meinst du, dass ich einfach das Programm in mehrere 64k grosse Dateien zerlege, oder ?
Dann muss ich auch jede einzelne Datei in einen eigenen Sektor der Floppy schreiben, damit ich die Datei(bzw. das "Unter"Programm) in den Speicher laden kann, oder ?
DAS MIT DER 8-bit-TECHNIK WERD ICH MIR AUF JEDEN FALL INS AUGE FASSEN
PS: Wie kann ich nachprüfen, ob mein Programm GENAU 64k gross ist ? Hoffentlich nicht nur so, indem ich mit einem HEX-Editor die Bits zähle...
das man ein ganz normales Dateisystem verwenden kann (das ist mir persönlich sehr wichtig
Ich persönlich würde auch gern ein Dateisystem für mein BS schreiben (am besten eines auf FAT12-Basis, damit ich wenigstens mit Windows formatierte Floppys auslesen kann) aber das ist, aus meiner Sicht, sehr kompliziert und es gibt meiner Ansicht nach auch keine Tutorials dafür, die das Erstellen eines Dateisystems, z.B. anhand eines NASM-Beispiels, treffend, kurz und präzise erklären
-
Die Blöcke müssen nicht unbedingt 64K groß sein.
Es geht darum, dass du beispielsweise ein Programm hast, was zu groß ist und es dann aufspaltest in mehrere kleine Bereiche, zwischen denen du umherschaltest. Die Bereiche liegen ja in verschiedenen Segmenten, also musst du wissen, welche Funktion an welcher Adresse in welchem Segment liegt.
Und genau diese Informationen musst du per Hand in deine Dateien schreiben, solange du kein richtiges Binärformat benutzt. Copy reicht da halt nicht.
Wenn du dein Programm kompilierst und es kommt eine Datei mit exakt 65536 Bytes Dateigröße raus, dann ist dein Programm genau 64 KB groß. ;-)
Ein Sektor auf der Floppy hat übrigens 512 Bytes und ist damit eine Größenordnung kleiner als die Overlaytechnik; wenn es dir also um die nächsten 512 Bytes geht, ist das bisschen übertrieben.
Ein Dateisystem auf FAT12-Basis ... du Scherzkeks, nimm doch direkt FAT12? Da gibt es auch Tutorials für.
Gruß,
Svenska
-
Es geht darum, dass du beispielsweise ein Programm hast, was zu groß ist und es dann aufspaltest in mehrere kleine Bereiche, zwischen denen du umherschaltest. Die Bereiche liegen ja in verschiedenen Segmenten, also musst du wissen, welche Funktion an welcher Adresse in welchem Segment liegt.
Das ist mir schon alles klar, aber damit ich mein BS von einer Floppy laden kann, muss ich doch erst mein Programm in 512er-Blöcke aufteilen, dann die einzelnen 512Byte-Files auf Sektoren der Floppy verteilen. (Damit ich dann die Sektoren (die Dateien) auch mit dem INT 13 in Bereiche des Speichers, bzw. in verschiedene Segmente, schreiben kann.)
Ein Dateisystem auf FAT12-Basis ... du Scherzkeks, nimm doch direkt FAT12? Da gibt es auch Tutorials für.
Schon, aber da hab ich bisher nur welche mit C-Code gefunden. Ich verwend eben Assembler.
Gruss,
bsc
-
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.
Was du willst, ist eine Routine, die ganz simpel 128 Sektoren in Folge hintereinander in den Speicher lesen kann, also 64K. Wie du dann weiterarbeitest, spielt keine Rolle. Am einfachsten erzeugst du eine (auf 512 Byte) gepaddete flache Binärdatei und kopierst die mit dd oder rawrite auf die Diskette. Fertig.
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... ;-)
Gruß,
Svenska
-
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 ?
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
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
-
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.
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 0
so 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. :)
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
-
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
-
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
-
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
-
Mit Schnittstelle meine ich das Ding zwischen Anwendung und Betriebssystem.
-
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:putstr
die Funktion "putstr" aufrufen ?
(wobei in beiden Versionen ES=4000)
Gruss,
bsc
-
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
-
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
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
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
-
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
-
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.
-
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.
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 ?
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 ?
-
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
-
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
-
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.
-
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.
-
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 ?
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
-
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
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.
-
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
-
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
-
Richtig. Der Prozessor springt da einfach hin, egal ob da nun Code ist oder nicht.
Im Speicher sein ist logisch, schließlich handelt es sich um die Syscall-Schnittstelle zum Kernel.
-
Mal ne andere Frage:
bin mir nicht ganz sicher, aber mit
tabelle: times 10 db 0
erstellt man glaub ich eine Tabelle namens tabelle, die 10 Bytes speichern kann und mit 0 initialisiert ist.
Ist es auch möglich, eine zweidimensionale Tabelle zu erstellen, d.h. eine Tabelle mit 2 Spalten, die z.B. in der 1.Spalte Byte-Werte speichert und in der 2.Spalte Word-Werte ?
Gruss,
bsc
-
Hi,
ja es ist möglich.
tabelle:
spalte1: times 10 db 0
spalte2: times 10 dw 0
Oder
tabelle: times 10 db 0, 0, 0
Dann ist der i-te Eintrag in Spalte 1 halt [tabelle + i * 3] und der i-te Eintrag in Spalte 2 [tabelle + i * 3 + 1]
Und natürlich weitere Varianten.
bin mir nicht ganz sicher, aber mit
tabelle: times 10 db 0
erstellt man glaub ich eine Tabelle namens tabelle, die 10 Bytes speichern kann und mit 0 initialisiert ist.
Das hier sind alles nur Folgen von Bytes. Dass meine Schnipsel es eine zweidimensionale Tabelle werden, ist nur das Resultat davon, wie du den Speicher adressierst.
-
Erstmal Danke für deine Hilfe, aber eins ist mir noch unklar:
Dann ist der i-te Eintrag in Spalte 1 halt [tabelle + i * 3]
Warum multiplizierst du i mit 3 ? Ist der i-te Eintrag in Spalte 1 nicht [tabelle+i] ?
Gruss und danke,
bsc
-
Hi,
tja das kommt auf die Interpretation an. Kann man auch mit [tabelle + i] machen. Dann liegt halt die zweite Spalte wo anders.
-
Ob du deine zweidimensionale Tabelle (=Array) hochkant stellst oder quer hinlegst, bleibt dir überlassen. Betrachte es einfach als eine Folge von Bytes (=eindimensional), wo du aber das Blatt abschneidest und untereinander packst.
Solange du dich überall auf eine(!) Denkweise festlegst, hast du später weniger Probleme, wenn du den Code auch warten musst.
-
Eigentlich wollt ichs hochkannt stellen, also zwei Spalten mit ca. 10 Zeilen.
Deshalb versteh ich auch nicht, warum es auf die Interpretation ankommt.
Betrachte es einfach als eine Folge von Bytes (=eindimensional), wo du aber das Blatt abschneidest und untereinander packst.
Das mit der Folge von Bytes ist mir schon klar, aber warum liegt bei [tabelle+i]
die zweite Spalte wo anders
?
Ich versteh eben nicht, was es mit der i*3
auf sich hat und warum dann die zweite Spalte woanders liegt. Die zweite Spalte liegt doch bei tabelle:
spalte1: times 10 db 0
spalte2: times 10 dw 0
normal an 2.Stelle, also auf deutsch nach der 1.Spalte, oder?
Sorry, aber an dieser Denkweise arbeit ich noch.
Gruss,
bsc
-
Das i*3 kommt daher, dass du 3 Einträge in einer Zeile hast.
Du kannst hingehen und sagen "das i. Element der n. Spalte ist [tabelle+n*BYTEPROZEILE+i]" oder aber "das i. Element der n. Spalte ist [tabelle+i*BYTEPROSPALTE+n]", je nachdem, ob du die Tabelle hoch oder quer legst. Entweder: Oder:
1 2 3 1 4 7
4 5 6 2 5 8
7 8 9 3 6 9
Was du im Speziellen einsetzt, hängt von deiner persönlichen Vorliebe ab; ich empfehle aber, konsistent zu bleiben. Beachte auch den Aufbau beim C-Compiler deines Vertrauens, damit sparst du dir später teure Konvertierungen, wenn du C und Assembler verbindest.
Gruß,
Sebastian
-
Das i*3 kommt daher, dass du 3 Einträge in einer Zeile hast.
Weil ein WORD im Speicher wie 2 Bytes gehandelt wird ?
Tabelle
4 2030
3 4000
7 1020
9 3342
1 4021
11 1231
2 3242
Also wenn ich jetzt die Zahl "1231" aus der Tabelle auslesen wollte, wäre [Tabelle+19], bzw. [Tabelle+6*3+1] richtig, wobei 6=Zeilennummer und 3=Bytes pro Zeile, oder (Wenn man davon ausgeht, dass man bei 1 anfängt zu zählen) ?
das i. Element der n. Spalte
Ich denk immer in der Kategorie : "das i.te Element der n. Zeile"
Thanks,
bsc
-
BYTE = 1 Byte, WORD = 2 Byte, DOUBLEWORD = 4 Byte, QUADWORD = 8 Byte.
Gilt in der Form uneingeschränkt nur auf x86-Assembler im 16-Bit-Modus.
Du zählst im Speicher grundsätzlich ab null (Basisadresse plus null), also ist das erste Element [tabelle+0].
Richtig ist also [tabelle+16] bzw. [tabelle+5*3+1].
-
Ja genau, wenn man, wie es in der Informatik üblich ist, bei 0 anfängt.
Ich hab jetzt meinen BIN-Kernel in einen OBJ-Kernel umgewandelt. Aber leider startet jetzt mein Kernel nicht mehr.
Ich habs so mit NASM versucht:
nasm -f bin -o boot.bin boot.asm
nasm -f obj -o krl.obj krl.asm
copy boot.bin+krl.obj os.img
Aber leider erscheinen jetzt nur noch die Meldungen des Bootloaders, dann ist schon Ende.
Wie kann ich meinen OBJ-Kernel starten ?
Gruss,
bsc
-
Siehe meine Antwort auf die PM. Bitte frage mich nicht direkt, sondern im Thread.
-
Hallo,
Ich hab jetzt meinen BIN-Kernel in einen OBJ-Kernel umgewandelt.
Warum?
Aber leider startet jetzt mein Kernel nicht mehr.
Das ist auch nicht verwunderlich. Oder kann der Teil in boot.bin die krnl.obj passend interpretieren? Das OBJ-Format ist sowas ähnliches wie ELF. Ich glaube sogar das OBJ für diesen Zweck völlig ungeeignet ist weil OBJ nur zum linken und nicht zum direkten ausführen dient.
Bitte beschäftige Dich erst mal eingehend mit den verschiedenen Executable/Object-Formaten und dann am besten mit GRUB.
Grüße
Erik
-
Ich hab jetzt meinen BIN-Kernel in einen OBJ-Kernel umgewandelt.
Weil ich in meiner FLAT-Binary keine externen Variablen mit GLOBAL und EXTERN definieren kann und somit die ganzen Adressen "PER HAND" auflösen muss.
Bitte beschäftige Dich erst mal eingehend mit den verschiedenen Executable/Object-Formaten und dann am besten mit GRUB.
Danke, aber mit GRUB werd ich mich nicht beschäftigen. Zum eigenen OS gehört (meiner Ansicht nach) auch ein eigener Bootloader.
Also ich brauch nur die Offsetadressen von ein paar externen Labels. Gibts da eine Möglichkeit in Bochs, das OS STEP BY STEP ausführen zu lassen und die Registerinhalte zu jedem STEP einzusehen ? Dann bräucht ich nicht jedesmal die ganzen Bytes zu zählen, bis ich beim Label bin, um zur Offset zu gelangen.
Gruss,
bsc
-
Ich hab jetzt meinen BIN-Kernel in einen OBJ-Kernel umgewandelt.
Weil ich in meiner FLAT-Binary keine externen Variablen mit GLOBAL und EXTERN definieren kann und somit die ganzen Adressen "PER HAND" auflösen muss.
Informiere dich mal weiter über Linker, und such bei denen die du findest nach einer Möglichkeit COM-Dateien zu erzeugen. Wenn du da mit den richtigen Parametern rangehst, solltest du das für einen Kernel verwenden können.
Gibts da eine Möglichkeit in Bochs, das OS STEP BY STEP ausführen zu lassen und die Registerinhalte zu jedem STEP einzusehen ?
Die Maschine im Bochs-Debugger starten. Mit dem Befehl lb 0x7c00 setzt du einen Breakpoint auf den Anfang des Bootsektors. (0x7c00 ist eine lineare Adresse. Du kannst auch einen Breakpoint auf den Kernel setzen, wenn du die Adresse entsprechend umrechnest.) Mit dem Befehl c (steht für continue) läuft dann die Maschine bis zum Breakpoint. Mit dem Befehl s (steht für step) kannst du einen Schritt ausführen (den angezeigten Befehl). Nach jedem Schritt kannst du dir mit regs die Register anzeigen lassen. Mit help bekommst du eine Befehlsübersicht. Mit z.B. help regs bekommst du eine Hilfe zu diesem Befehl. Die restlichen Befehle sind hier dokumentiert: http://bochs.sourceforge.net/doc/docbook/user/internal-debugger.html