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.