Brevet FR3131032A1 — publié le 20230623

Procédé de génération d’un fichier exécutable contenant des appels systèmes et librairie associée. Procédé de génération d’un fichier, exécutable en mode utilisateur et appelant une fonction système définit dans un fichier appelé, exécutable en mode noyau, consistant à : fournir (210) une librairie comportant un méta-modèle des appels systèmes, et un modèle instanciant le méta-modèle pour une fonction système particulière et implémentant une fonction « interne » associée ; éditer (220) un fichier source pour comporter un appel à la fonction « interne », l’exécution de la fonction « interne » appelant la fonction système et à renvoyer le résultat de l’exécution de la fonction système par le code appelé ; compiler (230) le fichier source avec la librairie, le compilateur vérifiant que l’appel de la fonction « interne » est conforme, la conformité à l’appel de la fonction système étant garantie par le modèle. Figure pour l'abrégé : Figure 2 Procédé de génération d’un fichier exécutable contenant des appels systèmes et librairie associée. La présente invention concerne le développement de logiciels. Elle concerne plus particulièrement l’étape de compilation permettant de générer un ou plusieurs fichier(s) exécutable(s) à partir d’un ou plusieurs fichier(s) source(s). Une fonction est une relation entre un ou plusieurs argument(s) d’entrée et un ou plusieurs argument(s) de sortie. Le contrat d’une fonction définit un certain nombre de contraintes sur cette fonction, en particulier son nommage, le nombre de ses arguments d’entrée, le type de ses arguments d’entrée, le nombre de ses arguments de sortie, le type de ses arguments de sortie. L’exécution d’une fonction est une tâche, paramétrée par les valeurs des arguments d’entrée, et qui retourne un résultat constitué des valeurs des arguments de sortie. Lorsqu’un fichier source comporte un appel à une fonction (c’est-à-dire une demande d’exécution d’une fonction), le compilateur vérifie d’abord que cet appel respecte le contrat de la fonction appelée. En cas de vérification négative, le compilateur rejette le code source comme incorrect et affiche une erreur. En cas de vérification positive, le compilateur génère le code de bas niveau (ou code objet) nécessaire à l’appel à la fonction dans le fichier exécutable, qui résulte de la compilation du fichier source. Le code objet généré par le compilateur sauvegardera les registres nécessaires aux arguments d’entrée et de sortie de l’appel à la fonction sur une pile associée à l’exécution lors de l’exécution de ce code objet. Cette vérification du compilateur est possible car la fonction et son contrat sont définis dans le fichier source à compiler, soit dans le fichier source lui-même, soit dans un fichier de librairie fourni pour la compilation (fichier « .c » pour la fonction et fichier « .h » pour son prototype en langage C/C++ par exemple, ou fichier « .rs » en langage RUST par exemple). Dans le présent document, on parle d’une fonction « interne » au sens où le compilateur dispose de toutes les informations nécessaires pour vérifier le bon respect du contrat d’une fonction lors d’un appel à cette fonction. Cependant, une telle vérification n’est pas possible pour ce que l’on peut appeler une fonction « externe », définie comme une fonction pour laquelle le compilateur ne dispose pas de toutes les informations lui permettant de vérifier que l’appel à une fonction respecte le contrat de la fonction appelée. Cette situation correspond au cas de premier et second fichiers exécutés en parallèle mais résultant de deux compilations séparées et indépendantes. Si le premier fichier appelle une fonction qui est exécutée par le second fichier et le second fichier retourne un résultat au premier fichier suite à l’exécution de cette fonction, alors le compilateur du premier fichier ne peut pas vérifier que l’appel à cette fonction respecte le contrat de cette fonction. De manière similaire le compilateur du second fichier ne peut pas vérifier que l’appel à cette fonction respecte le contrat de cette fonction. Cette situation se rencontre notamment lors des appels à des fonctions où le premier fichier exécutable est adapté pour être exécuté dans un premier mode d’exécution du processeur (tel que le mode « utilisateur »), tout en faisant appel à des fonctions offertes par un second fichier exécutable adapté pour être exécuté dans un second mode d’exécution du processeur, différent du premier mode d’exécution, et présentant souvent plus de privilèges (tel que le mode « superviseur » ou mode « noyau »). C’est le cas d’un premier fichier exécutable du type logiciel applicatif, appelant des fonctions dites systèmes offertes par un second fichier exécutable du type système d’exploitation. Le compilateur utilisé pour générer le fichier exécutable appelant ou appelé n’est donc pas en mesure de détecter une incohérence lors d’un appel à une fonction « externe ». Le compilateur n’est ainsi pas en mesure de garantir la génération d’un code de bas niveau adéquat. Ceci est un inconvénient majeur puisqu’on laisse alors au seul programmeur le soin d’écrire correctement, dans un fichier source, les appels à des fonctions externes, sans possibilité de vérification automatique par le compilateur. Par exemple, une mise à jour d’une fonction système occasionnant une modification de son contrat (par exemple un changement du nombre de ses arguments d’entrée) nécessite de revoir manuellement l’intégralité du code source d’un logiciel applicatif afin de vérifier que chaque appel à cette fonction système respecte les contraintes définies par son nouveau contrat. Or, l’appel à une fonction système correspond souvent à des instructions critiques pour le fonctionnement du logiciel applicatif et par conséquent pour le fonctionnement de l’ensemble du système mettant en œuvre ce logiciel applicatif. A titre d’exemple, supposons qu’un appel à une fonction système nécessite deux arguments d’entrée de la part d’un fichier appelant. Il est tout à fait possible qu’à la suite d’une erreur de codage, le fichier appelant ne fournisse qu’un seul argument d’entrée. Cette erreur n’est pas détectée à la compilation. La résolution de l’appel de la fonction système par le fichier appelé utilise alors silencieusement une valeur non définie pour le second argument. Ceci reste la plupart du temps sans conséquence, mais peut se révéler dans des situations particulières et avoir des conséquences imprévisibles. Ainsi, une mauvaise programmation des fonctions « externes » dans un code source est souvent synonyme de dysfonctionnements graves et/ou de failles de sécurité difficiles à détecter, d’autant plus lorsqu’ils se révèlent tardivement dans le cycle de développement du logiciel. On connaît des outils de vérification, tels que la vérification de modèle (« Model Checking » en anglais) ou la vérification formelle, qui permettent de garantir que les contrats des fonctions « externes » sont bien respectés. Cependant, la contrainte principale de ces outils est que leur mise en œuvre demande l'écriture de nombreuses annotations dans le code source uniquement compréhensible par l’outil de vérification lui-même et non pas par le compilateur. Ce travail d'annotation du code source est non trivial et constitue une charge de travail significative. De plus, l'utilisation de ces outils est totalement indépendante de la phase de compilation (le logiciel pouvant être générer même s’il ne respecte pas les annotations) et demande une double compétence, à la fois du langage du code source et de la sémantique de l’outils de vérification. De plus, la difficulté de mise en œuvre de ces outils croît significativement avec la taille du code à garantir. Ces outils s’accommodent généralement mal avec le besoin de maintenance évolutive d’un code : à chaque modification du code, tout le processus de vérification doit être repris. Il y a donc un besoin de pouvoir vérifier automatiquement le bon emploi d’une fonction « externe » (notamment d’une fonction système) dans un code source. Le but de la présente invention est de répondre à ce besoin. Pour cela l’invention a pour objet, côté appelant, un procédé de génération d’un fichier exécutable appelant, adapté pour être exécuté dans un mode appelant d’un processeur, tout en faisant appel à une fonction « externe » définit dans un fichier exécutable appelé, adapté pour être exécuté dans un mode appelé dudit processeur, le procédé consistant à : fournir une librairie comportant un méta-modèle définissant un prototype pour les appels à des fonctions « externes » et une pluralité de modèles, chaque modèle correspondant à une instanciation du méta-modèle pour une fonction « externe » particulière et implémentant une interface côté appelant définissant une fonction dite « interne » côté appelant et une interface côté appelé définissant une fonction dite « interne » côté appelé ; éditer un fichier source appelant du fichier exécutable appelant, le fichier source appelant comportant un appel à la fonction « interne » côté appelant, l’exécution par l’interface côté appelant de la fonction « interne » côté appelant consistant à appeler la fonction « externe » associée audit modèle et à renvoyer le résultat de l’exécution de ladite fonction « externe » par le code appelé ; compiler le fichier source appelant avec la librairie, le compilateur vérifiant que l’appel de la fonction « interne » côté appelant est conforme au contrat de ladite fonction « interne » côté appelant et la conformité au contrat de la fonction « externe » étant garantie par construction du modèle associé, de sorte que le fichier exécutable appelant obtenu est cohérent. L’invention a également pour objet, côté appelé, un procédé de génération d’un fichier exécutable appelé, adapté pour être exécuté dans un mode appelé d’un processeur, le fichier exécutable appelé définissant une fonction « externe » pouvant être appelée par un fichier exécutable appelant, adapté pour être exécuté dans un mode appelant dudit processeur, le procédé consistant à : fournir une librairie comportant un méta-modèle définissant un prototype pour les appels à des fonctions « externes » et une pluralité de modèles, chaque modèle correspondant à une instanciation du méta-modèle pour une fonction « externe » particulière et implémentant une interface côté appelant définissant une fonction dite « interne » côté appelant et une interface côté appelé définissant une fonction dite « interne » côté appelé ; éditer un fichier source appelé du fichier exécutable appelé, le fichier source appelé comportant un appel à la fonction « interne » côté appelé, l’exécution par l’interface côté appelé de la fonction « interne » côté appelé consistant à recevoir l’appel à la fonction « externe » associée audit modèle et à renvoyer le résultat de l’exécution de ladite fonction « externe » par le code appelé ; compiler le fichier source appelé avec la librairie, le compilateur vérifiant que l’appel de la fonction « interne » côté appelé est conforme au contrat de ladite fonction « interne » côté appelé et la conformité au contrat de la fonction système « externe » étant garantie par construction du modèle associé, de sorte que le fichier exécutable appelé obtenu est cohérent. Suivant des modes particuliers de réalisation, le procédé comporte une ou plusieurs des caractéristiques suivantes, prises isolément ou suivant toutes les combinaisons techniquement possibles : - l’appel à une fonction « interne » côté appelant ou côté appelé conduit l’interface correspondante côté appelant ou côté appelé à construire une instance des arguments d’entrées de la fonction « externe » avant d’appeler la fonction système « externe » sur ladite instance. - l’appel à une fonction « interne » côté appelant ou côté appelé conduit l’interface correspondante côté appelant ou côté appelé à renvoyer des arguments de sortie correspondant à ceux de la fonction « externe ». - la fonction « externe » est une fonction système. - le mode appelant est un mode utilisateur du processeur et le mode appelé est un mode superviseur du processeur. - le mode appelant est un mode superviseur du processeur et le mode appelé est un mode hyperviseur du processeur. - le mode appelant est un mode superviseur ou un mode hyperviseur du processeur et le mode appelé est un mode moniteur du processeur. - le fichier appelant est un logiciel applicatif et le fichier appelé est un système d’exploitation. - le fichier appelant est un système d’exploitation et le fichier appelé est un hyperviseur. - le fichier appelant est un système d’exploitation ou un hyperviseur et le fichier appelé est un moniteur d’environnement d’exécution de confiance. - le méta-modèle est défini par un « trait » du langage RUST, chaque modèle par une « structure » du langage RUST, et la fonction « interne » côté appelant ou côté appelé par une « fonction » du langage RUST. - côté appelé, tous les modèles sont identiques et ne diffèrent donc pas du méta-modèle. L’invention a également pour objet un fichier de librairie comportant un méta-modèle définissant un prototype pour les appels à des fonctions « externes » et une pluralité de modèles, chaque modèle correspondant à une instanciation du méta-modèle pour une fonction « externe » particulière et implémentant une interface côté appelant définissant une fonction dite « interne » côté appelant et une interface côté appelé définissant une fonction dite « interne » côté appelé. L’invention et ses avantages seront mieux compris à la lecture de la description détaillée qui va suivre d’un mode de réalisation particulier, donné uniquement à titre d’exemple non limitatif, cette description étant faite en se référant aux dessins annexés sur lesquels : La est une représentation schématique d’une structure possible des codes non-privilégié et privilégié permettant la mise en œuvre du procédé selon l’invention ; et, La est une représentation schématique sous forme de blocs du procédé . GENERALITES La représente schématiquement un programme dont l’exécution s’étend sur deux modes d’un processeur, soit un premier mode, dit mode non-privilégié – NP, soit un second mode, dit mode privilégié – P. Le procédé selon l’invention s’appuie sur la fourniture d’un premier fichier source, ou fichier non-privilégiée - NP, destiné à être exécuté dans le mode non-privilégié et d’un second fichier source, ou fichier privilégié – P, destiné à être exécuté dans le mode privilégié. Le fichier NP ou fichier appelant comporte un appel à une fonction externe, du fichier P ou fichier appelé. Plus précisément, le procédé selon l’invention s’appuie sur un méta-modèle des appels à une fonction externe. Le méta-modèle exprime les aspects communs à tous les appels à des fonctions systèmes. Le procédé selon l’invention s’appuie également sur une pluralité de modèles d’appel à une fonction externe. Chaque modèle de la pluralité de modèles est une instanciation du méta-modèle pour un appel à une fonction externe particulière. Un modèle encapsule l’appel à la fonction système associée et définit une interface d’accès côté NP, ou interface appelant et une interface d’accès côté P, ou interface appelé. Dans le code source, l’appel à une fonction externe se fait alors par l’appel à une fonction interne » de l’interface correspondante du modèle, fonction interne NP de l’interface appelant côté non-privilégié et fonction interne P de l’interface appelé côté privilégié. Les arguments de l’appel à la fonction interne NP sont des entrées NP et des sortie NP. Les arguments de l’appel à la fonction interne P sont des entrées P et des sortie P. Les arguments de l’appel à la fonction externe sont des arguments d’entrée et des arguments de sortie. Le méta-modèle et la pluralité de modèles sont prédéfinis. Ils sont avantageusement regroupés dans une librairie à disposition du compilateur du fichier NP et du compilateur du fichier P. Le mécanisme permettant de générer, lors de la compilation, les appels à des fonctions systèmes côté appelant et côté appelé correspondant à un simple appel à une fonction interne du code à compiler, ces fonctions internes peuvent donc être compilées dans leurs unités de compilation respectives tout en étant garanties comme étant cohérentes puisqu’elles relèvent d’un modèle prédéfini d’appel à une fonction système. La structure du code source va être présentée plus en détail dans un mode de réalisation utilisant le langage générique RUST, pour le fichier NP, d’une part, et pour le fichier P, d’autre part. Structure des codes source FICHIER NP On définit tout d’abord un méta-modèle constituant un prototype pour l’ensemble des appels possibles à des fonctions systèmes. En RUST, le méta-modèle est défini au moyen d’un « trait ». Un « trait » est une collection de méthodes définies pour un type de données quelconque, « Self ». Ces méthodes peuvent accéder à d'autres méthodes déclarées dans le même trait. Un trait peut ensuite être implémenté pour n'importe quel type de données. Côté NP, le méta-modèle est défini par un trait dénommé « SysCall ». Par exemple, le trait « SysCall » est défini de la manière suivante : // Trait utilisé pour définir le prototype des appels systèmes. pub trait SysCall { // le type retourné par l'appel système. type Out ; // l'identifiant de l'appel système. const NBR : u16 ; // prototype du code de bas niveau de l'appel système. fn call ( self ) -> Self :: Out ; } On définit ensuite un modèle pour un appel à une fonction système particulière. Par exemple, on définit un modèle pour un appel à une fonction système d’impression, c’est-à-dire la transmission d’un message à un pilote d’un périphérique d’impression sur un écran ou une imprimante. En RUST, un modèle est défini au moyen d’une structure « struct ». Il s’agit ici d’un type particulier de données prenant la forme d’un multiplet de variables. Le modèle d’un appel à la fonction système d’impression peut alors être défini par la structure « SysPrint » suivante : // Définition de la structure SysPrint qui contient les arguments d'entrée de l'appel système. struct SysPrint { pub msg : * const u8 ; // Le pointeur vers le message texte à afficher. pub len : usize ; // La longueur du message. } Ici, la structure « SysPrint » est définie par deux variables, respectivement le massage « msg » à imprimer (ou plutôt le pointeur vers le message à imprimer) et la longueur « len » de ce message. Ces deux variables sont en fait les arguments d’entrées de la fonction système d’impression. Le modèle d’un appel à la fonction système d’impression reçoit ensuite par exemple l’implémentation suivante : impl SysPrint { // Constructeur de la structure SysPrint à partir d'une chaine de caractères. pub fn new ( s : & str ) -> Self { Self { msg : s . as_ptr (), len : s . bytes (). len () } } } La méthode « new » permet de créer une nouvelle instance du type de données « SysPrint » à partir de la variable « s », qui est du type « chaîne de caractères ». On définit également la manière dont se comporte le modèle lorsqu’il est considéré sous l’angle du méta-modèle. En RUST, cela revient à implémenter le trait « SysCall » pour la structure de données « SysPrint ». Côté NP, cette implémentation est par exemple définie de la manière suivante : // Implémentation du trait SysCall pour la structure SysPrint. impl SysCall for SysPrint { type Out : (); //Aucun paramètre de retour const NBR : u16 = 255 ; //identifiant de la fonction système appelée fn call ( self ) -> Self :: Out { // Code de bas niveau permettant d'effectuer l'appel système. XXX } } Est ici crée une interface côté NP à travers laquelle accéder à l’appel à la fonction système associée au modèle. Est ainsi définie la sortie NP lorsque l’interface NP est appelée. Dans l’exemple ci-dessus, l’interface NP définit par la structure « SysPrint » associée à la fonction système d’impression renvoie une sortie NP « Out » vide. L’identifiant de la fonction système associée au modèle est instancié. Dans l’exemple ci-dessus, la fonction système d’impression que la structure « SysPrint » appelle est identifiée par la valeur 255. Est ici instancié le code fonctionnel du mode NP permettant de réaliser effectivement l’appel à la fonction système associée au modèle. Dans l’exemple ci-dessus, ce code fonctionnel n’est pas détaillé et on l’a indiqué par « XXX ». L’interface NP lance l’appel à la fonction système associée au travers de l’utilisation de la méthode « call » de la structure « SysPrint ». Enfin, on définit un appel à l’interface NP pour initier l’appel à la fonction système associée. En RUST, l’appel à la fonction système d’impression côté NP passe par l’appel à une fonction « interne » « sys_print » recevant par exemple l’implémentation suivante : // Implémentation de l'appel système pour le code utilisateur. pub fn sys_print ( s : & str ) -> SysPrint as SysCall >:: Out { SysPrint :: new ( s ). call () } Cette fonction « interne » porte sur un argument d’entrée « s » du type chaîne de caractères. On la dénomme entrée NP. L’exécution de cette fonction « interne » conduit à l’application de la fonction « new » de la structure « SysPrint » sur l’argument d’entrée « s » pour obtenir une nouvelle instance de la structure « SysPrint », puis à l’application de la fonction « call » sur cette nouvelle instance de la structure « SysPrint ». Lors de l’appel à cette fonction « interne », il y a donc création automatique d’une instance de la structure « SysPrint ». On est donc certain que les arguments d’entrée de l’appel à la fonction système seront conformes au contrat de cette fonction système. L’appel à cette fonction « interne , retourne, en tant que sortie NP, l’argument de sortie « Out » du trait « SysCall » tel qu’implémenté pour la structure « SysPrint ». Lorsque cette fonction « interne » est appelée, son résultat est par construction une sortie conforme au contrat de la fonction système associée au modèle. Lors de la compilation du code source côté NP, le compilateur vérifie que le contrat de la fonction « sys_print » est respecté. Cependant ce contrat est minimal puisqu’il consiste à vérifier la dénomination de la fonction (« sys_print »), qu’il y a un argument d’entrée du type chaîne de caractères, et qu’il y a un argument de sortie du type correspondant à la sortie prévue pour le modèle. Ainsi, la vérification par le compilateur du contrat de la fonction « interne » conduit toujours à une vérification positive, tout en étant certain que l’appel à la fonction système s’effectue conformément au contrat de la fonction système, grâce à l’implémentation correcte du modèle. Ainsi le modèle apparaît comme un objet formant interface, masquant l’appel à la fonction « externe ». Dans le code source, l’appel à la fonction « externe » passe donc maintenant par l’interrogation de l’interface NP, cette interrogation prenant la forme d’une fonction « interne » au code source NP. L’interface NP répond à cette interrogation en réalisant l’appel à la fonction « externe». Cette interface NP étant programmée une fois pour toute, tout appel à la fonction « externe » sera effectué sans erreur. FICHIER P Le trait « SysCall » et la structure « SysPrint », décrits ci-dessus, sont partagés entre codes appelant et appelé. Côté P, le méta-modèle est en outre défini par un trait. Ce trait est dénommé « Context ». Par exemple, le trait « Context » est définit de la manière suivante : //Trait utilisé pour traiter de façon générique un appel système reçu par le code privilégié pub trait Context { fn syscall_in T : SysCall >(& self ) -> & T ; fn syscall_out T : SysCall >( & mut self , res : & T :: Out ); fn syscall T : SysCall , F >(& mut self , f : F ) where F : FnOnce (& T ) -> T :: Out { let input = self . syscall_in :: T >(); let output = f ( input ); self . syscall_out :: T >( & output ); } } Ici, les méthodes rentrant dans la définition du trait « Context » sont génériques. Elles sont définies pour toute structure de données « T » implémentant le trait « SysCall ». La méthode « syscall » permet de récupérer les informations relatives à l’appel à la fonction système dans la pile d’exécution du fichier NP (identifiant de la fonction appelée et arguments d’entrées) et de déposer les informations relatives au résultat de l’appel à la fonction système dans la pile d’exécution du fichier NP (arguments de sortie). Côté P, un modèle, comme le modèle de l’appel à la fonction système d’impression, ne reçoit pas de définition particulière, car tous les modèles côté P se comportent d’une même manière, définie par le trait « Context ». En effet, il s’agit simplement après avoir récupérer les informations relatives à l’appel à la fonction système, de lancer l’exécution de la fonction système appelée, puis de renvoyer la sortie de la fonction système une fois exécuté. C’est donc le trait « Context » qui définit l’interface côté P. En RUST, côté P, l’appel à l’interface P s’effectue par une fonction « sys_print », recevant par exemple l’implémentation suivante : // Implémentation de l'appel système pour le code privilégié. fn sys_print CTX : Context >( ctx : & mut CTX ) { ctx . syscall T : SysPrint >( XXX// Code privilégié traitant l'appel système et qui doit // respecter le contrat définie par le trait SysCall. ); } Est ici instancié le code fonctionnel du mode P permettant de réaliser effectivement la fonction système associée au modèle. Dans l’exemple ci-dessus, ce code fonctionnel, totalement dépendant de l'implémentation, n’est pas détaillé et on l’a indiqué par « XXX » La fonction « sys_print » est une fonction « interne » permettant d’interroger l’interface P et de lancer l’exécution de la fonction système au travers de l’utilisation de la méthode « syscall » d’une instance « ctx » du trait « Context », la méthode « syscall » générique étant ici spécifique à une structure de données « T » respectant le modèle « SysPrint ». Lors de la compilation du code source côté P, le compilateur vérifie que le contrat de la fonction « sys_print » est respecté, c’est-à-dire le contrat défini par le trait « SysCall » Ainsi, la vérification par le compilateur du contrat de la fonction « interne » conduit toujours à une vérification positive, tout en étant certain que l’appel à la fonction système reçu est conforme au contrat de la fonction système, grâce à l’implémentation correcte du modèle. PROCEDE La représente un mode de réalisation du procédé 200 de génération d’un fichier exécutable selon l’invention. Dans une étape 210, est fournie une librairie comportant le méta-modèle définissant un prototype pour les appels à des fonctions « externes » et une pluralité de modèles, chaque modèle correspondant à une instanciation du méta-modèle pour une fonction « externe » particulière. Chaque modèle définit une interface NP, respectivement une interface P, pouvant être interrogée par une fonction dite « interne » NP, respectivement une fonction dite « interne » P. L’étape 220 suivante consiste à éditer un fichier source côté NP (respectivement P), comportant un appel à la fonction « interne » NP (respectivement P), l’exécution par l’interface NP (respectivement P), de la fonction « interne » NP (respectivement P) consistant à appeler la fonction « externe » associée audit modèle et à renvoyer le résultat de l’exécution de ladite fonction « externe ». Une fois le code source édité, il est compilé (étape 230) avec la librairie fournie à l’étape 210. Le compilateur vérifie que l’appel de la fonction « interne » NP (respectivement P) est conforme au contrat de la fonction « interne » non-privilégié NP (respectivement P). La conformité au contrat de la fonction système « externe » étant garantie par construction du modèle associé à cette fonction « externe », de sorte que le fichier exécutable NP (respectivement P) obtenu est cohérent. VARIANTES et GENERALISATION Un processeur possède différents modes d’exécution. Chaque mode a des privilèges ou droits (accès aux registres, instructions, etc.). Le mode de réalisation présenté ci-dessus a été présenté pour le cas de deux modes : un mode utilisateur pour les applications et un mode superviseur pour le système d’exploitation. D’où l’appellation de mode « non-privilégié » pour le mode utilisateur et de mode « privilégié » pour le mode superviseur. Cependant, dans les architectures actuelles, le nombre de modes d’un processeur est souvent supérieur à deux. Par exemple, un processeur peut présenter un mode utilisateur, un mode superviseur, un mode hyperviseur et un mode moniteur. De plus, il peut exister une hiérarchie entre les modes. Par exemple, les privilèges et les droits de l’hyperviseur incluent ceux du superviseur, qui eux-mêmes incluent ceux de l’utilisateur. Mais ceci n’est pas obligatoire et l’on peut rencontrer le cas de deux modes d’exécution de privilèges et droits disjoints, qui ne s’incluent pas. Ainsi, la présente invention s’applique plus largement à tout code appelant exécuté dans un premier mode (ou mode appelant) et un code appelé exécuté dans un second mode (mode appelé). En effet, selon l’invention, le code appelant peut s’exécuter dans un mode autre que le mode utilisateur (ou « non-privilégié ») et le code appelé peut s’exécuter dans un mode autre que le mode superviseur (ou « non-privilégié »). L’invention s’applique à des codes appelant et appelé compilés séparément et s’exécutant dans des modes d’exécution différents. Mais l’invention s’applique également à des codes appelant et appelé pouvant s’exécuter dans un même mode d’exécution du processeur mais résultant de deux compilation séparées. C’est par exemple le cas d’un pilote (« driver » en anglais) compilé séparément et s’exécutant en mode superviseur qui appelle d’autres fonctions systèmes s’exécutant également en mode superviseur. Ainsi, le mode appelant peut être un mode utilisateur du processeur et le mode appelé, un mode superviseur du processeur ; ou le mode appelant peut être un mode superviseur du processeur et le mode appelé, un mode hyperviseur du processeur ; ou encore le mode appelant peut être un mode superviseur ou un mode hyperviseur du processeur et le mode appelé, un mode moniteur du processeur. Ainsi, le fichier appelant peut-être un logiciel applicatif et le fichier appelé, un système d’exploitation ; ou le fichier appelant peut être un système d’exploitation et le fichier appelé un hyperviseur ; ou encore, le fichier appelant peut être un système d’exploitation ou un hyperviseur et le fichier appelé, un moniteur d’environnement d’exécution de confiance (« Trusted Execution Environment »). Si dans le mode de réalisation décrit en détail ci-dessus, on a parlé de fichier en tant que synonyme de code. En toute rigueur, un unique code peut-être écrit dans plusieurs fichiers. Si dans le mode de réalisation décrit en détail ci-dessus, on a pris l’exemple d’une fonction système d’impression, l’invention s’applique à toute fonction système et, plus largement, à toute fonction externe. AVANTAGES La présente invention évite la rupture de la chaîne de vérification du typage des fonctions côtés appelant et appelé en apportant des moyens de vérification de cohérence appelant/appelé, à la compilation, sans surcoût à l’exécution. L’invention se fonde sur la traduction des contraintes sur les appels à des fonctions « externes » en des contraintes sur des appels à des fonctions « internes » qui sont vérifiables par le compilateur. Les appels de fonction externe entre l’appelant et l’appelé sont ainsi mis en œuvre à l’image d’une fonction standard. La vérification de ces contraintes de construction par le compilateur garantissent la cohérence des appels de fonctions externes du côté de l’appelant et de l’appelé, à l’image d’un appel de fonction au sein de la même unité de compilation. Le processus de compilation est donc en mesure de rejeter du code mal formé qui pourrait entrainer des comportements erratiques futurs et des failles de sécurité dormantes. Avec la présente invention, un programmeur spécifie un méta-modèle (une interface générique) pour tous les appels de fonctions externes pour qu’ils respectent un même format. Cette interface générique est ensuite traduite en prototype pour chaque appel de fonction externe possible. C’est bien le méta-modèle, qui constitue l’unité de compilation commune, qui formalise le contrat des appels de fonctions externes. Cette unité est utilisée pour la compilation du code appelant d’une part et celle du code appelé d’autre part. Pour la mise en œuvre particulière en langage RUST, la notion de trait permet de déclarer des interfaces génériques que le code peut ensuite utiliser et dont les contraintes sont vérifiées par le compilateur. Mais ces traits ne peuvent s’appliquer que sur des structures de données et non sur des fonctions. Cette limitation est contournée en convertissant les paramètres de l’appel à une fonction externe en deux structures de données liées (une pour les paramètres d’entrées fournis par l’appelant et une pour les paramètres de sorties fournis par l’appelé). Si le langage RUST a été utilisé dans la présentation du mode de réalisation préféré, l’invention peut être mise en œuvre dans tout langage de programmation générique. La mise en œuvre de l’invention n’a aucun impact sur les performances de la compilation. Il est important de noter que ces vérifications n’ont aucun surcoût lors de la mise en œuvre par un système embarquant les logiciels ainsi compilés, puisque tout est réalisé en amont, lors de la conception et la compilation des logiciels. En revanche, la vérification automatique que permet l’invention arrive au plus tôt dans le cycle de développement. La détection d’une incohérence devient possible dès la phase de compilation. L’invention permet donc à un programmeur de logiciel de mettre en œuvre les spécifications des appels de fonctions externes de manière à pouvoir en faire l’appel et la résolution de manière sûre, automatique, dès la compilation et sans surcoût à l’exécution. Les applications de la présente invention concernent tout développement logiciel comportant des codes compilés séparément. Le plus couramment, un code exécuté dans un mode peu privilégié (comme le mode « utilisateur »), pour des raisons de sûreté et sécurité, souhaite faire appel à une fonctionnalité dont la mise en œuvre est faite à un plus haut niveau de privilège (comme le mode « noyau » ). Cela peut donc s’étendre à tout domaine industriel (aéronautique, automobile, militaire) faisant appel à des développements nécessitant de conserver la maîtrise sur toutes les couches logicielles, de la couche applicative jusqu’à celle exploitant le matériel. Il est à noter que le côté non-privilégié peut-être compilé de manière indépendante du côté privilégié. Notamment, alors que le fichier exécutable du logiciel d’exploitation reste inchangé, on réalise des mises à jour du fichier exécutable du logiciel applicatif, au cours par exemple de son développement. L’inverse est également vrai. Procédé de génération d’un fichier exécutable appelant, adapté pour être exécuté dans un mode appelant d’un processeur, tout en faisant appel à une fonction « externe » définit dans un fichier exécutable appelé, adapté pour être exécuté dans un mode appelé dudit processeur, le procédé consistant à : - fournir (210) une librairie comportant un méta-modèle définissant un prototype pour les appels à des fonctions « externes » et une pluralité de modèles, chaque modèle correspondant à une instanciation du méta-modèle pour une fonction « externe » particulière et implémentant une interface côté appelant définissant une fonction dite « interne » côté appelant et une interface côté appelé définissant une fonction dite « interne » côté appelé ; - éditer (220) un fichier source appelant du fichier exécutable appelant, le fichier source appelant comportant un appel à la fonction « interne » côté appelant, l’exécution par l’interface côté appelant de la fonction « interne » côté appelant consistant à appeler la fonction « externe » associée audit modèle et à renvoyer le résultat de l’exécution de ladite fonction « externe » par le code appelé ; - compiler (230) le fichier source appelant avec la librairie, le compilateur vérifiant que l’appel de la fonction « interne » côté appelant est conforme au contrat de ladite fonction « interne » côté appelant et la conformité au contrat de la fonction « externe » étant garantie par construction du modèle associé, de sorte que le fichier exécutable appelant obtenu est cohérent. Procédé de génération d’un fichier exécutable appelé, adapté pour être exécuté dans un mode appelé d’un processeur, le fichier exécutable appelé définissant une fonction « externe » pouvant être appelée par un fichier exécutable appelant, adapté pour être exécuté dans un mode appelant dudit processeur, le procédé consistant à : - fournir (210) une librairie comportant un méta-modèle définissant un prototype pour les appels à des fonctions « externes » et une pluralité de modèles, chaque modèle correspondant à une instanciation du méta-modèle pour une fonction « externe » particulière et implémentant une interface côté appelant définissant une fonction dite « interne » côté appelant et une interface côté appelé définissant une fonction dite « interne » côté appelé ; - éditer (220) un fichier source appelé du fichier exécutable appelé, le fichier source appelé comportant un appel à la fonction « interne » côté appelé, l’exécution par l’interface côté appelé de la fonction « interne » côté appelé consistant à recevoir l’appel à la fonction « externe » associée audit modèle et à renvoyer le résultat de l’exécution de ladite fonction « externe » par le code appelé ; - compiler (230) le fichier source appelé avec la librairie, le compilateur vérifiant que l’appel de la fonction « interne » côté appelé est conforme au contrat de ladite fonction « interne » côté appelé et la conformité au contrat de la fonction système « externe » étant garantie par construction du modèle associé, de sorte que le fichier exécutable appelé obtenu est cohérent. Procédé selon l’une quelconque des revendications précédentes, dans lequel l’appel à une fonction « interne » côté appelant ou côté appelé conduit l’interface correspondante côté appelant ou côté appelé à construire une instance des arguments d’entrées de la fonction « externe » avant d’appeler la fonction système « externe » sur ladite instance. Procédé selon l’une quelconque des revendications précédentes, dans lequel l’appel à une fonction « interne » côté appelant ou côté appelé conduit l’interface correspondante côté appelant ou côté appelé à renvoyer des arguments de sortie correspondant à ceux de la fonction « externe ». Procédé selon l’une quelconque des revendications précédentes, dans lequel la fonction « externe » est une fonction système. Procédé selon l’une quelconque des revendications 1 à 5, dans lequel le mode appelant est un mode utilisateur du processeur et le mode appelé est un mode superviseur du processeur. Procédé selon l’une quelconque des revendications 1 à 5, dans lequel le mode appelant est un mode superviseur du processeur et le mode appelé est un mode hyperviseur du processeur. Procédé selon l’une quelconque des revendications 1 à 5, dans lequel le mode appelant est un mode superviseur ou un mode hyperviseur du processeur et le mode appelé est un mode moniteur du processeur. Procédé selon l’une quelconque des revendications 1 à 8, dans lequel le fichier appelant est un logiciel applicatif et le fichier appelé est un système d’exploitation. Procédé selon l’une quelconque des revendications 1 à 8, dans lequel le fichier appelant est un système d’exploitation et le fichier appelé est un hyperviseur. Procédé selon l’une quelconque des revendications 1 à 8, dans lequel le fichier appelant est un système d’exploitation ou un hyperviseur et le fichier appelé est un moniteur d’environnement d’exécution de confiance. Procédé selon l’une quelconque des revendications précédentes, dans lequel le méta-modèle est défini par un « trait » du langage RUST, chaque modèle par une « structure » du langage RUST, et la fonction « interne » côté appelant ou côté appelé par une « fonction » du langage RUST. Procédé selon l’une quelconque des revendications précédentes, dans lequel, côté appelé, tous les modèles sont identiques et ne diffèrent donc pas du méta-modèle. Librairie comportant un méta-modèle définissant un prototype pour les appels à des fonctions « externes » et une pluralité de modèles, chaque modèle correspondant à une instanciation du méta-modèle pour une fonction « externe » particulière et implémentant une interface côté appelant définissant une fonction dite « interne » côté appelant et une interface côté appelé définissant une fonction dite « interne » côté appelé.