[Kos-dev] loader

Christophe Avoinne kos-dev@enix.org
Thu, 21 Sep 2000 22:45:57 +0200


Salut,

Concernant la gestion des modules,

il y a pas mal de choses qui me gènent :

- l'exportation explicite par un tableau de chaine des noms des
fonctions à exporter qui fait vraiment double emploi, d'autant que cela
ajoute probablement des entrées de relogement inutiles.

- l'importation des symboles me parait un peu maladroite. D'autant que
l'on voudrait avoir la possibilité ou non d'être "sensible" à la
réaffectation d'une valeur d'un symbole. Je crains un amoncellement
injustifié de ces entrées.

mais plutôt que de continuer à en faire la liste, j'aimerais mieux
discuter des solutions possibles.

1) trois sections : .init, .load et .zero :

Ces sections rassemblent le code, les données de la façon suivante :

- tous le code des routines d'initialisation et des données non
persistante, i.e, rendus inutiles après l'initialisation du module sont
placés dans la section .init. Au moment du lien, il n'est pas nécesaire
d'allouer un segment, car on en a déjà une image. Quant au relogement,
il peut être effectué sur l'image directement.

- tous le code et les données initialisées persistants sont placés dans
la section .load qui est en fait un condensé dans l'ordre de .text,
.rodata et .data. En revanche, il faut allouer un segment, y copier
l'image, puis faire le relogement sur le segment et non l'image. Le
segment doit être lisible, inscriptible et exéctable et en mode
superviseur (la protection contre l'écriture d'un segment de code en
mode superviseur est un luxe dont on se passerait volontier, d'autant
que cela nous "empêcherait" d'y recopier l'image !). Par ailleurs, si on
choisit de prendre des segments dans la zone des 256 Mo en raison de
l'association identité linéaire-physique, ces pages seront forcément
marquées inscriptibles, rendant de fait la distinction .text, .rodata et
.data non nécessaire.

- toutes les données non intialisées (remplies de zéro en fait) de type
.bss, sera placées dans la section .zero. Il faut également allouer un
segment et le remplir de zéro. Le relogement, à l'instar de la section
.load s'effectue dans le segment.

exemple de module.ld (testé avec succès) :

...
PHDRS
  {
    INIT PT_LOAD; /* a une image dans le fichier */
    LOAD PT_LOAD; /* idem */
    ZERO PT_NULL; /* n'a pas d'image dans le fichier */
  }
SECTIONS
  {
    .init
      {
        *(.ctors) /* table des pointeurs sur les fonctions
d'initialisation à appeler, en général une seule */
        LONG(0) /* marque de fin de la table précédente */
        *(.init.text) /* code des fonctions initialisatrice non
persistante */
        *(.init.data) /* données non persistantes pour le besoin
d'initialisation - RARE */
      }:INIT
    .load
      {
        *(.dtors) /* table des pointeurs sur les fonctions de
terminaison à appeler, en général une seule */
        LONG(0) /* marque de fin de la table précédente */
        *(.fini.text) /* code des fonctions de terminaison */
        *(.fini.data) /* données de terminaisons - RARE */
        *(.text) /* code normal où on peut trouver aussi les 2 types de
fonctions importées */
        *(.rodata) /* chaines constantes de caractère, etc. */
        *(.data) /* variables également exportables */
      }:LOAD
    .zero
      {
        *(.bss)
        COMMON
      }:ZERO
  }
...

à utiliser avec :
gcc ... -o my_module.o -Wl,-r,-T module.ld -fno-common -fnostdlib one.o
two.o ...

2) les fonctions ou variables exportables :

- les symboles globals; ils ne sont pas communiqués aux autres modules;
cela permet de créer un seul fichier objet à partir de plusieurs
fichiers objets ayant des fonctions définies globalement que l'on ne
souhaite pas rendre publique aux autres modules. Il ne retiendra donc
pas, après l'édition de lien, les noms et les entrées de ces symboles
dans un segment persistant.

- les symboles exportés, définis avec '__attribute__((weak))'; ils
seront en revanche rendus publiques aux autres modules; Il faudra
retenir après l'édition de lien, les noms et les entrées de ces symboles
dans un segment persistant.

Pour qu'un symbole soit réellement exporté entre les modules, il suffit
de faire :

#define __export__ __attribute__((weak))

int __export__ ma_variable_exportee;

int __export__ ma_fonction_exportee(...) {...};

Pour déterminer les symboles exportés et son nombre, il suffit de
scanner dans la section .symtab, les entrées de symbole ayant un "TYPE"
NO_TYPE,OBJECT ou FUNCTION (0,1 ou 2), un "BIND" WEAK (2), et un "SHNDX"
non UNDEF (!= 0, les index des sections .load ou .zero, en fait). Une
fois, le nombre et les symboles déterminés, il est trivial de récupérer
leur nom dans la section .strtab.

Conclusion, il faut créer un segment qui contiendra les entrées de ces
symboles et un autre segment pour leurs noms.

Note : un symbole défini dans la section .init n'est pas exportable du
fait qu'il ne persiste pas après l'édition de lien.

3) il existent deux types de fonctions importables :

- un symbole peut être importé et résolu une fois pour tout (pas de
segment de relogement persistant à garder) à l'édition de lien; Si, par
la suite, un autre module devait redéfinir ce symbole, les modules qui
ont importé ce symbole de cette façon ne sont pas affectés par ce
changement. On utilise la définition "extern". Le nom du symbole doit
être un symbole exporté bien évidemment. C'est le comportement habituel
dans une édition de lien classique.

#define __extern__ extern

int __extern__ ma_variable_importee;

int __extern__ ma_fonction_importee(...);

- un symbole peut être importé et être sensible au changement
d'affectation ultérieure du symbole par un autre module. Pour cela, le
module crée les entrées de relogement persistantes nécessaires dans un
segment, et ajoute ces entrées dans la liste des entrées de relogement
de ce symbole. Si, par la suite, un autre module devait redéfinir ce
symbole, les modules qui ont importé ce symbole de cette façon
inspecteront toutes les entrées de relogement de ce symbole pour en
modifier le code des modules qui ont importé ce symbole de cette façon.
On utilise la définition "extern __attribute__((weak))".

#define __import__ extern __attribute__((weak))

int __import__ ma_variable_importee;

int __import__ ma_fonction_importee(...);

Pour déterminer les symboles importés et leur nombre d'entrée de
relogement persistantes, il suffit de scanner dans la section .symtab,
les entrées de symbole ayant un "TYPE" NO_TYPE (0), un "BIND" WEAK (2),
et un "SHNDX" UNDEF (0). Pour chaque symbole importé de la deuxième
façon, on recherche l'existence d'un symbole exporté de ce nom dans un
autre module, puis on détermine le nombre d'entrée de relogement
nécessaires pour ce symbole depuis la section de relogement .rel.load du
module importateur (il ne peut pas en avoir dans .rel.zero, puisque
cette section est initialisée à zéro au démarage, ni dans .rel.init
puisque le code de ce dernier est appelé à disparaître). On a donc un
segment qui devra contenir ces entrées. Chacune de ces entrées seront
liés par une sous-liste qui sera ajoutée à la liste des entrées de
relogement du symbole concerné.

Note : un symbole défini dans la section .init n'est pas importable du
fait qu'il ne persiste pas après l'édition de lien.

exemple :

struct imported_symbol_type
  {
    struct exported_symbol_type
      *symbol;
    unsigned long
      type; /* 32 ou PC_32 */
    struct imported_symbol_type
      *next;
  }imported_symbole_type;

struct exported_symbol_type
  {
    const char
       *name;
    unsigned long
       value;
    ...
    struct imported_symbol_relocation_entry_type
       *list;
    ...
  }exported_symbol_type;

Pour deux symboles importés DUMMY et FOO dans un module, j'ai trois
entrées de relogement pour le premier et deux pour le second dans la
section .rel.load, je crée donc un segment de 5 entrées de relogement
(struct imported_symbol_relocation_entry_type), je lie les trois du
premier dans la liste du symbole DUMMY et les deux du second dans la
liste du symbol FOO (via les champs next).

Chaque symbole contiendra donc ou non une liste des entrées de
relogement en provenance d'un ou plusieurs modules qui l'importent. Le
parcours de la liste des entrées de relogement d'un symbole exporté
suffit à mettre à jour tous les modules importateurs de ce symbol
(attention aux problèmes du multi-threading, aïe, aïe ! je vois une
solution pour les prévenir qui peut marcher avec les fonctions mais pas
avec les variables).

Conclusion :

- un symbole ne peut être importé - sensible ou non - que s'il existe un
symbole exporté depuis un autre module;
- un symbole exporté peut être importé depuis un autre module sans être
sensible, dans ce cas,
  la résolution des liens est faite une fois pour tout avec la valeur de
ce symbole à ce moment là.

3) Conclusion :

il faut par module une image finale contenant :
  - un segment pour .load;
  - un segment pour .zero;
  - un segment pour la description du module;
  - un segment pour les noms des symboles exportés (un .strtab ne
contient pas que des noms de symboles exportés mais aussi globals);
  - un segment pour les entrées des symboles exportés;
  - un segment pour les entrées de relogement pour les symboles importés
sensibles;

C'est tout, je crois.

J'ai peut-être oublié des choses en passant, mais je crois avoir dit
l'essentiel.

Amicalement.

Hlide.