[Kos-dev] [David Decotigny <David.Decotigny@irisa.fr>] Re: Probleme avec le C++

d2 kos-dev@enix.org
28 Nov 2002 08:05:36 +0100


Hello,

Pour repondre a Anthony Jaguenaud (peux-tu te presenter ?), notre
probleme n'est pas celui-la. Notre probleme est que nous avons un OS
qui se presente sous la forme de modules objets (format ELF, comme les
.o normaux) lies au chargement de l'OS (par le "Kos loader"), et que
nous desirons controler precisement quels symboles nous voulons qu'un
module M1 "exporte" vers les autres modules Mx : quels symboles de M1
les autres modules Mx peuvent utiliser. Voir la doc
(http://kos.enix.org/~d2/snapshots/kos_current/doc/modules-html/) pour
plus de precisions. Or, ce que tu rappelles (et que nous faisons deja,
c'est oblige), c'est la technique pour qu'on puisse appeler du C a
partir du C++ (ie desactiver localement le name mangling sur certains
symboles importes depuis le C++). Nous voulons qqch de different :
nous voulons avoir un moyen de recuperer le nom de symbole C++
(surtout les noms de methodes) genere par le compilo (et pour ca on ne
peut pas desactiver le name mangling puisque c'est precisement du
vraiment vrai C++), pour le mettre dans une table de symboles exportes
speciale, histoire que les modules C++ puissent appeler les methodes
C++ (exportees) des classes qui sont definies par d'autres modules. Et
ca, c'est pas extern "C" qui nous le permettra.

>>>>> "Julien" == Julien Munier <mejj@enix.org> writes:
    Julien> en particulier, je voulais savoir si tu pouvais me donner
    Julien> plus de details sur le travail a realiser dans le loader

    Julien> n'hesite pas a donner un maximum de conseils car cela fait
    Julien> bien longtemps que je n'ai pas regarde le loader.

Note que je l'ai reecrit cet ete, donc ca a change des choses par
rapport a la version precedente. Note aussi qu'il y a une doc d'apercu
sur la doc d'intro (url ci-dessus). Le loader est toujours decompose
en petites operations qui sont executees par vagues (operation 0 sur
tous les modules, puis operation 1 sur tous les modules, etc...). Pour
les modules elf32, les operations sont donnees dans _elf32.c (avec
l'underscore ;).

Une premiere grosse difference avec la version precedente est que
toutes les sections sont considerees : qu'elles s'appellent .text,
.tototototo, .tata, ... du moment qu'il y a le flag SHF_ALLOC, elles
seront chargees en memoire par le loader, et toutes les stringtable,
symtable et tables de relocation (reltable dans la suite) associees
seront alors considerees. Les autres sections (.stab, ...) ne sont,
elles, pas chargees puisqu'elles n'ont pas le flag => y'a juste 3
sections speciales qui sont marquees *en plus* (elles gardent
SHF_ALLOC) avec un flag a nous (SHF_KOS_ALLOC_IN_INIT_HEAP) : les
sections figurant dans la macro INIT_HEAP_SECTIONS_LIST (en principe
.init.text, .init.data et .ctor), c'est pour se rappeler qu'elles sont
a allouer dans la zone des sections d'init (the "init heap"), liberee
apres initialisation de tous les modules du noyau. Toutes les sections
chargees (SHF_ALLOC) sont traitees uniformement.

Une deuxieme grosse difference avec la version precedente : les
marshalls des symboles d'exportes, des initlevels et des
cleanuplevel. Avant, si tu te rappelles, c'etait des tableaux qui se
situaient au debut de .init et de .text, avec des marqueurs speciaux
pour detecter le debut, la fin, etc... et qu'il fallait reperer,
mesurer, allouer une zone dest, charger, et reloger a la
main. Desormais, tous ces marshalls sont fabriques directement par le
compilo et se trouvent dans des sections dediees :
resp. .kfunctab/.kvartab (fonctions/variables exportees), .ctor
(initlevels), .dtor (cleanuplevels). En effet, puisque (on vient de le
dire) toutes les sections "dignes d'interet" (SHF_ALLOC) sont chargees
ou il faut *et* relogees, on est sur que, une fois les etapes de
relocation passees, ces marshalls seront directement a jour ! Plus
besoin de faire une pseudo-relocation a la main comme on faisait
avant. C'est pourquoi la relocation se fait en 2 temps : intra-module
pour tous les modules d'abord, histoire que toutes les sections
"dignes d'interet" soient relogees *y compris* les marshalls
.kvartab/.kfunctab des symboles exportes (le plus important !), puis
inter-module ensuite, histoire que les autres modules puissent
recuperer les adresses des symboles exportes par les autres
modules. Entre les 2, il y a une etape supplementaire pour enregistrer
les symboles exportes dans une hash table du loader (supprimee au
lancement du noyau), histoire que le lookup de symboles lors de la
relocation inter-module aille plus vite.

Une des implications de cette technique est qu'on n'a pas un nombre
d'initlevel/cleanuplevels limite (limite = MAX_INT = environ 2
milliards) ! De meme, on peut avoir plusieurs init_module() avec le
meme initlevel dans un seul et meme module. Idem cleanuplevel. Ca
autorise a avoir un gros .o qui rassemble tous les modules, et qui a
ete au prealable reloge par le linker ld de l'hote (linux/solaris/...)
=> cf modules/kos.ro : c'est un seul module qui contient en realite le
code de tous les autres, qui a donc un seul marshall des initlevels et
des cleanuplevels (ie une seule section .ctor/.dtor), mais on a par
exemple plusieurs init_module pour l'initlevel 1, plusieurs pour
l'initlevel 2... L'ordre d'appel des init_module pour un initlevel
donne etant defini par l'ordre d'apparition dans .ctor, lui-meme
defini par l'ordre d'apparition des modules sur la ligne de commande
de ld au moment ou on a construit le super-module kos.ro. Une des
perspectives a tout ca, c'est que je voulais proposer un mecanisme de
chargement alternatif a grub+kos_loader, qui travaille directement a
partir d'un bootsecteur tout bete, donc sans edition de lien a faire :
juste charger le kos.ro truc a une adresse fixe en virtuel, et hop,
tout est dedans, y compris la routine d'appel des init_modules (dans
le module bootstrap).

Revenons au loader. En C dans le texte, le principe est de commencer
par recuperer la taille necessaire pour placer le code/donnees des
zones d'init et de run-time (operation "alloc") par etude des
differentes sections (pour evaluer la taille necessaire des
differentes sections), et par etude des symboles a conserver (symboles
importes + exportes + debug, pour la construction des marshalls). La
phase suivante est de copier/initialiser les zones de texte+donnee (y
compris bss). Puis viennent les 3 phases interessantes :
  - relocation intra-module : tout se passe dans link.c, ou
    elf32_relocate_local() appelle relocate_section() avec le flag
    RELOC_LOCAL sur *toutes* les sections de relocation (flag SHT_REL
    ou RELA reliees a des sections "dignes d'interet", ie avec le
    SHF_ALLOC). Le but est de faire la relocation sur tous les
    symboles qui sont internes au modules (*symname=='\0' ||
    sym->st_shndx != SHN_UNDEF). L'interet est que, a l'issue de
    l'operation, les sections de symboles exportes/.dtor/.ctor qui
    contenaient de simples tableaux de structures { ptr vers nom, ptr
    vers fonction } est elle aussi relogee => ca tombe bien parce que
    ca correspond precisement au marshall des symboles
    exportes/initlevel/cleanup ! En gros, c'est le compilo qui s'est
    occupe de tout bien alloue dans le binaire, on n'a plus qu'a
    reutiliser.
  - mise a jour des marshalls de symboles : le but est d'appeler
    ld_build_expsym_hashtable(), commune a tous les types de modules
    (elf32, et les autres => dans ../libld/) en fin de fonction
    elf32_update_tables() pour mettre a jour la hashtable des symboles
    exportes qui sert a l'operation suivante : la relocation
    externe. Pour cela, on s'interesse a 2 sections speciales (si
    presentes) : .kfunctab et .kvartab qui sont toutes les deux des
    tableaux d'elements du type module_symbol (mod.h), et qui viennent
    d'etre relogees par l'operation precedente. Une fois qu'elles sont
    reperees, il suffit d'indiquer dans la struct kernel_module ou
    elles commencent, et hop ! ld_build_expsym_hashtable()
    peut les utiliser telles quelles sans recopie prealable, sans
    modif, sans rien !  On en profite pour reperer egalement
    .ctor et .dtor, si elles existent.
  - relocation inter-module : on se preoccupe des symboles undefined,
    qui n'ont pas encore ete reloges. C'est toujours
    relocate_section() avec le flag RELOC_IMPORT sur *toutes* les
    sections de relocation (flag SHT_REL ou RELA liees a des sections
    "dignes d'interet", ie avec le SHF_ALLOC) qui est appellee. Mais
    cette fois-ci, pour chaque symbole indefini, on appelle
    ld_lookup_expsym(), la fonction qui fait le lookup dans la
    hashtable precedemment construite.

Il faut savoir que ce code est partage avec le mod_check, ie le
programme utilisateur sous l'hote (linux/solaris) qui fait les verif
hors-ligne, et peut generer les graphes dot de dependances
(http://kos.enix.org/~d2/snapshots/kos_current/deps.html). Pas mal
d'autres choses sont partagees, dont libld : seule la gestion de la MM
est differentes (voir _utils.h).

Bon, concernant notre histoire, je pense que l'idee serait que pour
les symboles exportes, on fonctionne differemment. Pour le moment,
c'est tres simple : le compilo a lui-meme genere les sections
.kfunctab et .kvartab qui contiennent directement les marshalls des
symboles exportes (apres relocation intra-module). La, ca va etre
different, du coup. Il va surement falloir passer par une etape
intermediaire qui consiste en la creation des marshalls proprement
dite. L'idee est de decouper text, data en 2 : une partie avec les
symboles exportes (mettons .export.text, et .export.data), et une
partie "normale" (comme avant : .text/.data/.rodata/.bss/...). Et
l'idee est de se servir des entrees de la stringtable et de la
symtable qui concernent .export.text/.export.data pour remplir les
marshalls des symboles exportes. Ca modifierait 3 choses :
  - operation init : pour evaluer la taille des marshalls a allouer a
    partir de la symtable et de la stringtable pour les symboles des
    .export.*
  - operation build : pour allouer les marshalls, et indiquer a
    quelles adresses ils ont ete mis (=> maj champs idoines du
    struct kernel_module)
  - operation update_table : remplissage des marshalls a partir de la
    stringtable et de la symtable, pour les symboles de .export.*

Et evidemment, ca veut dire qu'il faut supprimer les macros
EXPORT_FUNCTION() et EXPORT_VARIABLE(), pour les remplacer par des
macros #define EXPORTED_FUNCTION __attribute__((section(".export.text")))
a utiliser dans les *proto* des fonctions et des methodes (ie dans les
.h), du style : void toto(int) EXPORTED_FUNCTION; Idem methodes. Je
sais pas ou il vaut mieux mettre le EXPORTED_FUNCTION : entre void et
nom ? Avant void ?  Idem pour les variables.

Voila. Je pense pas que ce soit complique, d'autant que le code du
loader a minci, et, je l'espere, s'est proprifie (c'etait /aussi/ le
but).

Bonne matinee (suis ici depuis 6h, c'est beau une ville le matin),

-- 
d2