Dies ist eine alte Version des Dokuments!
The linker32 is written for 32 bit platforms. The linker performs various tasks like:
As a first step the linker must be initialized with the method init(). What happens is:
Each class has a constant block. Certain interfaces need a reduced constant block and arrays just need a type descriptor, see Type Descriptor. The constant block holds information about the class, which is constant:
The const pool contains values of type float and double which are not placed directly into the code. After all elements are added the constBlockSize can be determined and the indices (Item.index
) of all elements will be fixed. It's important to note that all elements, which are addresses (AddressItem
, ConstantBlockItem
, …), simply contain a reference to the according item. Only later after address fixing can the real addresses be inserted!
Diese Methode muss vom Launcher für jede Klasse aufgerufen werden.
Das Laufzeitsystem holt sich aus der Systemtabelle für alle Klassen die Startadresse des Konstantenblockes und kann dann daraus die Startadresse des Klassenkonstruktors (benötigt der Kernel für das Booten) oder auch die globalen Pointer dieser Klasse (benötigt der Heap für die Garbage Collection) holen. codeBase gibt die Startadresse der ersten Methode einer Klasse an, codeSize ist die Grösse aller Methoden einer Klasse summiert. Davon ausgenommen sind Systemmethoden, die in der Konfiguration definiert sind, und die einen dort fixierten Offset haben (Exception Handlers).
Der Konstantenblock ist als Liste aufgebaut (Item). Die Basisklasse für die Listeneinträge ist ch.ntb.inf.dee.linker.ConstBlkEntry
. Je nach Art des Eintrages wird eine der folgenden Klassen verwendet:
AddressEntry
: Für Einträge, die eine Adresse repräsentieren.ConstantEntry
: Für Einträge, die eine Konstante repräsentieren (StdConstant).FixedValueEntry
: Für Einträge, die einen festen Wert repräsentieren (z.B. eine Grössenangabe).InterfaceEntry
: Für Einträge, die einen Interface-Eintrag repräsentieren (Interface-ID und Offset).StringEntry
: Für Einträge, die einen konstanten String repräsentieren (StringLiteral).OffsetEntry
: Für Einträge in die Tabelle allen Instanzpointern.Der Typdeskriptor wird benötigt um Instanzmethoden zu finden und um Typenprüfungen durchführen zu können. Der Aufbau und die Funktionsweise ist auf einer eigenen Seite beschrieben: Type Descriptor.
Der Stringpool beinhaltet die konstanten Strings einer Klasse. Dabei werden die Strings folgendermassen abgelegt (siehe auch unter Strings:
[ tag ] [ stringClassAddress ] [ Object ] [ nofChars ] [ chars ] [ ⋮ ]
Dabe werden die einzelnen Zeichen jeweils in 2-Byte-Unicode-Kodierung abgelegt.
Der Konstantenpool beinhaltet die Konstanten einer Klasse. Zur Zeit werden nur Gleitkomma-Zahlen (Float & Double) im Konstantenblock abgelegt. Ganzzahlen werden direkt in den Code integriert. Die Gleitkommazahlen werden wie in Java an IEEE 754 angelehnt abgelegt. Dabei wird bei einer 64bit Zahl das Highword an der niedrigeren Adresse abgelegt.
Beispiel:
[40490FDB] 3.1415927 (float) [401921FB] 6.283185307179586 (double) [54442D18]
Bevor die Memory Map fixiert werden kann, müssen noch ein paar Grössen und Offsets pro Standard-Klasse berechnet werden:
void calculateCodeSizeAndOffsets(Class)
: Diese Methode berechnet die Grösse des Maschinencodes für die gesamte Klasse und setzt das Feld offset
jeder Methode dieser Klasse. Dieses beschreibt den Byte-Offset des Maschinencodes einer Methode im Speicher ausgehend von der Startaddresse des Codes der Klasse. Wenn der Offset einer Methode direkt von der Konfiguration vorgegeben ist (für Exception-Handler-Code) wird diese Methode nicht in die Berechnung der Codegrösse miteinbezogen. Der durch die Konfiguration vorgegebene Offset bezieht sich nicht auf die Basis einer Klasse, sondern auf die Basis des zugehörigen Segmentes.void calculateGlobalConstantTableSize()
: Berechnet die Grösse der globalen Konstanten-Tabelle.Der Linker stellt für das gesamte System eine Systemtabelle zusammen. Diese muss an eine fixe Adresse geladen werden und enthält wichtige Informationen für das Laufzeitsystem. Grundsätzlich muss dabei zwischen drei Fällen unterschieden werden:
Die Methode void createSystemtable()
erstellt die Systemtabelle. Diese ist als doppelt verkettete Liste realisiert und verwendet die selben von BlockEntry
abgeleiteten Listenelemente wie die Konstantenblöcke der Klassen, zusätzlich noch die Klasse SysTabEntry
. Diese referenziert den Konstantenblock einer Klasse.
Die Systemtabelle hat den folgenden Aufbau:
Bevor die Systemtabelle zusammengestellt werden kann, muss der Klassenkonstruktor der Kernel-Klasse gefunden werden. Dazu wird der Klassennamen aus der Konfiguration geladen und anschliessend die Methode <clinit>
gesucht.
Nun kann die Systemtabelle aufgebaut werden. Dazu wird als erstes der Offset zum Beginn der Klassenliste (Liste mit der Basisadresse des Konstantenblocks jeder Klasse) eingefügt. Anschliessend werden die Informationen zu Heap und Stack eingefügt. Anschliessend die Anzahl vorhandener Klassen und die bereits erwähnte Liste mit den Basisadressen der Konstantenblöcke. Die Systemtabelle wird mit dem Wert 0x0 abgeschlossen.
Der letzte Teil mit den Referenzen auf den Konstantenblock der einzelnen Klassen ist wie folgt zusammengestellt. Zuerst kommen alle Klassen mit einem Klassenkonstruktor. Diese Klassen sind bereits in der korrekten Reihenfolge gemäss class_initialization sortiert. Anschliessend kommen alle nicht-initialisierten Klassen. Arrays und Interfaces haben keinen Konstantenblock und kommen nicht in die Liste mit Ausnahme von Interfaces, die einen Klassenkonstruktor haben, siehe Interfacemethoden in type_descriptor.
Beim MPC555 wird die Systemtabelle in der Dual-Mapped-Section direkt nach dem Exceptioncode positioniert (Addresse 0x2000 → siehe Memory Map).
Das Platzieren des Codes, der statischen Variablen und der Konstanten im Speicher geschieht wie folgt. Als erstes wird jeder Klasse ein Memory-Segment für den Code (Class.codeSegment
), die statischen Variablen (Class.varSegment
) und die Konstanten (Class.constSegment
) zugeteilt. Welche Segmente das sind, wird der Konfiguration entnommen. Dabei läuft der Linker rekursiv durch die möglichen Segmente und wählt das erste passende aus. Während dieser Zuteilung wird der im Segment benutzte Platz gespeichert (Segment.usedSize
) und der entsprechende Offset (Class.codeOffset
, Class.varOffset
und Class.constOffset
) zugeteilt. Auch für jedes Array und für jedes benötigte Interface wird ein Segment (Class.constSegment
) für den Typedescriptor gesucht und der Offset (Array.offset, Class.offset) gesetzt.
Anschliessend wird die verwendete Grösse auch noch für das Systemtabellen-Segment gesetzt und das Speichersegment für die globale Konstantentabelle festgelegt.
In einem weiteren Schritt wird die Grösse für jedes verwendete Segment festgelegt (sofern nicht bereits in der Konfiguration vorgegeben). Nun kann für jedes verwendete Segment die Basisadresse berechnet werden.
Achtung: Ganz zu Beginn müssen Systemmethoden, die einen in von Konfiguration gegebenen Offset haben (Exception-Handlers), bereits im passenden Segment platziert werden. Das führt zu „Löchern“ in diesem Segment. Diese Löcher können durch eine effizientere Allokation eliminiert werden, was aber zur Zeit nicht gemacht ist.
Nachdem der Code, die statischen Variablen und der Konstantenblock jeder Klasse und auch die Systemtabelle und die globale Konstantentabelle platziert sind, werden nun die absoluten Adressen berechnet. Dazu steht die Methode void calculateAbsoluteAddresses(Class)
bereit.
In einem ersten Schritt werden die Adressen für die statischen Felder bestimmt. Dazu läuft der Linker durch die entsprechende Liste (Class.classFields
) durch und berechnet die absolute Adresse (Item.address
). Die Adresse wird nur bei nicht konstanten Feldern gesetzt.
Im nächsten Schritt werden die Adressen der Methoden berechnet. Dabei müssen die durch die Konfiguration absolut positionierten Methoden (Exception-Handlers)wiederum speziell behandelt werden.
Als drittes werden die Adressen für die Konstanten im Constant pool und die Strings im String pool berechnet.
Als letztes wird noch die Adresse der Klasse selbst (Class.address
) auf den Eintrag size im Typ-Deskriptor gesetzt.
Diese Methode muss vom Launcher für jede Klasse aufgerufen werden.
Nach dem Festlegen der Adressen kann nun der Konstantenblock aktualisiert werden. Dazu legt die Methode void updateConstantBlock(Class)
als erstes die Basisadresse des Codes (codeBase) fest. Anschliessend werden auch noch die Basisadresse für die Klassen-Variablen (varBase
) und die Gesamtgrösse der Klassen-Variablen (varSize
) festgelegt. Als letztes wird noch die Grösse eines Objektes der Klasse (size) im Type descriptor aktualisiert.
In der globalen Konstantentabelle werden Konstanten gespeichert, die nicht zu einer Klasse gehören, sondern z.B. zur Laufzeit für die Konvertierung von int zu float verwendet werden.
Zum Schluss wird das Target Image erstellt. Dabei wird für jede Methode jeder Klasse und für Konstantenblock (einer pro Klasse, Array oder Interface) je ein TargetSegment erzeugt und in einer Liste abgelegt.
Die Felder index, offset und address der Klasse ch.ntb.inf.deep.classItems.Item
werden, je nach dem was Item repräsentiert, anders verwendet. Die untenstehende Liste gibt einen Überblick, wann welches Feld wie verwendet wird.
Bemerkung: Angaben in Eckigen Klammern bezeichnen den Typ. Beispiel: [Class] bedeutet das dieses Objekt vom Typ ch.ntb.inf.deep.classItems.Class
ist.
Konstante Referenzen
Beispiel:
static final Object o = new Object();