Brevet FR2877454A1 — publié le 20060505

PROCEDE DE CHARGEMENT D'UN CODE LOGICIEL EN LANGAGE INTERMEDIAIRE ORIENTE OBJET DANS UN APPAREIL PORTATIF La présente invention concerne les logiciels et environnements d'exécution embarqués dans un appareil portatif et en particulier les procédés de conversion de tels logiciels lors de leur chargement dans l'appareil portatif pour exécution. Un appareil portatif désignera par la suite tout appareil numérique portatif doté d'un microcontrôleur réunissant un processeur et des mémoires, tel qu'une carte à puce par exemple. La volonté de créer des applications inter opérables a conduit au développement des langages intermédiaires de programmation orientée objet. Le but principal de ces langages est ainsi de rendre leurs logiciels indépendants du matériel sur lequel ils doivent être exécutés. Les logiciels sont alors prévus pour s'exécuter sous la forme d'un code intermédiaire indépendant de l'architecture sous-jacente. Les programmeurs sont ainsi globalement dégagés des contraintes liées à des matériels spécifiques. La diffusion d'une même version de logiciel pour différents matériels est également possible. Les langages intermédiaires orientés objet tels que le pseudo-code Java (désigné par bytecode en langue anglaise), obtenu après compilation du langage source Java, ont ainsi connu un essor conséquent. On peut également citer le langage intermédiaire MSIL (acronyme anglo-saxon pour Microsoft intermediate langage ) déployé dans le cadre de l'environnement.net ou DotNet (marque déposée), obtenu après compilation de divers langages sources possibles tels que C++ ou C#. Le code intermédiaire correspond donc classiquement à une forme compilée du logiciel. Ces codes intermédiaires ne peuvent pas être exécutés tel quels par le processeur de l'appareil sur lequel on désire lancer un programme compilé sous forme de code intermédiaire. Il est nécessaire d'introduire une couche logicielle ayant pour principale fonction d'interpréter le code intermédiaire en instructions exécutables par le processeur de l'appareil hôte. Cette couche logicielle est appelée machine virtuelle . Par exemple, la machine virtuelle JAVA permet l'exécution d'un logiciel Java sur une plate-forme donnée sur laquelle elle est implémentée. Pour ce faire, la machine virtuelle dispose d'un jeu d'instructions propre, chaque instruction étant codée sur un ou plusieurs octets. Le jeu d'instructions de la machine virtuelle comporte par exemple un certain nombre d'instructions classiques comme les opérations arithmétiques, logiques et les sauts. Par ailleurs, la machine virtuelle dispose d'une pile d'exécution. En effet, elle ne comprend aucun registre utilisateur, que le programmeur du logiciel pourrait utiliser directement. A la place, on utilise des variables locales numérotées à partir de zéro. La figure 1 illustre un exemple de moyens mis en oeuvre pour charger un logiciel compilé 10 sous forme 30 d'un code intermédiaire source dans une carte à puce 20, qui constitue en l'occurrence l'appareil portatif. La carte à puce est munie d'une machine virtuelle pour l'exécution de tels logiciels. Une partie de la machine virtuelle, plus particulièrement nommée chargeur 30, est prévue pour charger en mémoire de travail de la carte à puce 20, à partir d'un appareil source 40, par exemple un serveur auquel la carte est connectée par des moyens quelconques non représentés, le code logiciel 10 compilé en langage intermédiaire orienté objet. Le chargeur 30 est une application qui est par exemple stockée dans une mémoire non volatile de l'appareil portatif. Lors du chargement d'une méthode par exemple du code intermédiaire source 10 sur la carte par la machine virtuelle, celle-ci est convertie en un code interne 50, dit code destination par la suite. Typiquement, les informations du code intermédiaire 10 sont converties en des instructions en code machine 50 directement exécutables par le processeur du système considéré, en l'occurrence la carte à puce. Le code destination peut également être sous forme d'un code intermédiaire converti En effet, dans certains environnements tels que l'environnement.net, le code intermédiaire MSIL est destiné à être traduit en code machine par un compilateur Juste à temps ( Jit compiler en terminologie anglosaxonne). Pendant le chargement d'une méthode, une conversion du code intermédiaire source 10 en un code intermédiaire interne visant à rendre l'exécution de la méthode plus efficace peut être mis en oeuvre. Par exemple, dans le cas d'un logiciel compilé en langage MSIL, le code d'instruction Add pour l'addition est susceptible d'agir sur des nombres entiers, flottants ou autres. Ainsi, si le code intermédiaire source n'est pas modifié, il sera nécessaire de conserver des informations de typage pour pouvoir déterminer lors de l'exécution quel type de nombre le logiciel est en train de manipuler lorsqu'une instruction Add est rencontrée. La transformation du code d'instruction Add dans le code intermédiaire source en un code d'instruction dans le code destination indiquant directement l'information de typage associée à l'opération d'addition, par exemple Add Float , permettra alors de faire l'exécution sans qu'il soit nécessaire de conserver l'information de typage. Toutefois, du fait de la conversion opérée lors du chargement sur la carte des instructions d'une méthode du code intermédiaire source, les instructions correspondantes du code destination, constitué soit par le code machine exécutable par le processeur de la carte, soit par un code intermédiaire converti, ne vont plus nécessairement conserver la même taille que les instructions dans le code source. En effet, un code d'instruction peut par exemple être converti en plusieurs séquences d'instructions. Un problème se pose alors dans le cas de la présence d'instructions de saut dans une méthode du code intermédiaire source destiné à être converti. En effet, dans le code intermédiaire, les adresses de destination des instructions de saut sont exprimées en déplacements positifs ou négatifs en termes d'octets par rapport au début de la méthode, pour indiquer l'instruction à l'adresse cible visée. Par exemple, une instruction de saut peut consister à indiquer au programme qu'il faut sauter à l'instruction qui se trouve 5 octets en avant par rapport au début de la méthode dans la structure de données en mémoire de travail dans laquelle est chargée la méthode du code intermédiaire source. Aussi, comme la taille des instructions est amenée à changer dans le code destination lors de l'étape de chargement (et de conversion) du code intermédiaire source, il est nécessaire dans ce cas de réajuster les paramètres des instructions de saut décrivant les adresses de destination de saut. Il faut ainsi pouvoir recalculer les déplacements prévus en octets dans le code destination compte-tenu de la taille des nouvelles instructions converties, de façon à indiquer dans le code destination les bonnes adresses de destination de saut correspondant aux paramètres des instructions de saut. Une solution de l'art antérieur consiste typiquement, durant le chargement du code, à élaborer et stocker en mémoire des tables de conversion, permettant d'associer, pour chaque méthode chargée, les anciennes adresses de destination cibles de saut dans le code intermédiaire source aux nouvelles adresses de destination de saut correspondantes dans le code destination. Il faut alors prévoir une entrée dans la table pour chaque adresse de destination de saut. Cependant, l'utilisation de cette solution présente des inconvénients. Elle implique en effet de monopoliser une certaine quantité de mémoire vive, utilisée comme mémoire de travail, pour stocker les tables de conversion et la quantité de mémoire requise est de plus proportionnelle au nombre d'adresse de destination de saut différentes dans le code. Aussi, si cette étape de chargement (et de conversion) de logiciels sous forme de code intermédiaire, basée sur l'utilisation de telles tables de conversion, est réalisable sur la plupart des ordinateurs de bureau présentant des ressources importantes de mémoire vive et de processeur, permettant ainsi de stocker les tables sans pénaliser pour autant l'exécution du logiciel en mémoire vive, certains systèmes devant embarquer des logiciels en code intermédiaire ont des capacités beaucoup plus limitées, qui empêchent l'utilisation des tables. Ainsi, du fait des contraintes de miniaturisation, une carte à puce comprend typiquement entre 1 et 4 KiloOctets de mémoire vive (utilisée comme mémoire de travail), entre 32 et 64 KiloOctets de mémoire non volatile réinscriptible (utilisée comme mémoire de travail et comme mémoire de masse) et environ 256 KiloOctets de mémoire morte (utilisée pour stocker le code de l'environnement d'exécution). Une telle carte à puce est donc inadaptée à un fonctionnement qui impliquerait de stocker des tables de conversion pour recalculer les adresses de destination de saut dans le code destination. En effet, pour des méthodes comprenant un grand nombre de sauts, la taille de la table deviendrait un problème susceptible d'empêcher le chargement du programme en mémoire. Une autre solution pour de tels système consiste alors à utiliser un format de code destination qui ne modifie pas les déplacements objets des instructions de saut par rapport au code intermédiaire source qui est chargé. On utilise typiquement un format de code interne converti où les instructions du code destination ont la même taille que celles du code intermédiaire source, de façon à ne pas avoir à recalculer les déplacements objets des sauts par l'intermédiaire de tables de conversion. C'est la solution généralement adoptée par la machine virtuelle Java dans le cadre de la spécification JavaCard, 1.5 permettant de faire tourner la technologie Java sur les cartes à puce et autres appareils à mémoire limitée. Cependant, cette solution n'est pas toujours envisageable. La présente invention vise à résoudre les inconvénients précités. L'invention a ainsi pour objet un procédé de chargement d'un code logiciel source compilé en langage intermédiaire orienté objet dans un appareil numérique portatif muni d'une machine virtuelle d'exécution, comprenant une étape de conversion de la suite d'instructions dudit code source pour générer un code destination formé de ladite suite d'instructions converties, et une étape de mise à jour du code destination comprenant la détermination, pour chaque instruction de saut convertie, de la nouvelle adresse de destination de saut dans le code destination correspondant à l'adresse de destination de saut de l'instruction de saut correspondante dans le code source, caractérisé en ce oue la détermination de la nouvelle adresse de destina:ion de saut dans le code destination comprend une analyse simultanée des codes source et destination prenant en compte la taille des instructions du code source et celle des instructions converties correspondantes du code destination. Selon un mode de réalisation, l'analyse simultanée des codes source et destination pour la détermination de la nouvelle adresse de destination de saut dans le code destination comprend les étapes suivantes: - lire la première instruction du code source et ajouter la taille de ladite instruction à un index de source préalablement initialisé à zéro; ajouter la taille de l'instruction convertie correspondante dans le code destination à un index de destination préalablement initialisé à zéro; pour chaque instruction suivante du code source, lire ladite instruction et mettre à jour l'index de source en ajoutant la taille de ladite instruction et l'index de destination en ajoutant la taille de l'instruction convertie correspondante dans le code destination; réitérer l'étape précédente jusqu'à ce que la 25 valeur de l'index de source soit égale à l'adresse de destination de saut de l'instruction de saut correspondante dans le code source, et - mettre à jour la nouvelle adresse de destination de saut dans le code destination avec la valeur de 30 l'index de destination. Selon un mode de réalisation particulier, l'ajout de la taille de l'instruct_on convertie correspondante dans le code destination à l'index de destination comprend une étape préalable de lecture de ladite instruction dans le code destination. De préférence, le code destination est un code en langage intermédiaire. Dans une variante, le code destination est un code machine exécutable par l'appareil portatif. L'invention concerne également un chargeur d'un code logiciel source compilé en langage intermédiaire orienté objet destiné à être chargé dans un appareil numérique portatif, caractérisé en ce qu'il est susceptible d'être stocké dans une mémoire non volatile de l'appareil pour la mise en oeuvre du procédé selon l'invention. L'invention concerne encore un appareil numérique portatif comprenant une mémoire non volatile mémorisant le chargeur tel qu'il vient d'être décrit. De préférence, cet appareil est une carte à puce. D'autres caractéristiques et avantages de la présente invention apparaîtront plus clairement à la lecture de la description.. suivante donnée à titre d'exemple illustratif et non limitatif et faite en référence aux figures annexées dans lesquelles: -la figure 1 illustre un exemple de moyens mis en oeuvre pour charger un code logiciel compilé sous forme d'un code intermédiaire dit code source, dans une carte à puce et a déjà été décrite; -la figure 2 est un organigramme résumant les principales étapes du procédé de l'invention selon un mode de réalisation préféré. Par la suite, on détaillera plus particulièrement un exemple de réalisation pratique, dans le cas d'un logiciel compilé sous la forme d'un code intermédiaire MSIL pour son chargement sur une carte à puce, bien que l'invention s'applique également pour des logiciels compilés dans d'autres langages intermédiaires orientés objet, tel que Java. Ainsi, l'exemple suivant correspond à une méthode écrite en langage C#, prévue pour exécuter une boucle et appeler de manière répétée la fonction f() ou g() selon que la variable i est paire ou impaire: public static void example() int i; for(i=0; i Ces adresses sont donc les adresses qui posent problème et qui vont devoir être mises à jour car il y a des instructions de saut dans le code intermédiaire qui les prennent en adresse de destination. Ainsi, à l'adresse 0002, on trouve l'instruction br.s , qui correspond à un saut et qui prend 0019 en paramètre indiquant l'adresse de destination de saut correspondante. A l'adresse 0007, on trouve l'instruction brtrue.s , qui correspond à un saut et qui prend 0010 en paramètre indiquant l'adresse de destination de saut correspondante. A l'adresse 000e, on trouve l'instruction br.s , qui prend 0015 en paramètre indiquant l'adresse de destination de saut correspondante. Enfin, à l'adresse OOlc, on trouve l'instruction blt.s , qui correspond à un saut et qui prend 0004 en paramètre indiquant l'adresse de destination de saut correspondante. Ces adresses de destination de saut devront donc être mises à Jour après conversion du code intermédiaire vers un code destination par le chargeur. Selon l'exemple, la méthode est convertie dans le code destination suivant: example nParams 0.nLocals 1.maxStack 2 0000: LDC1 0 _ 0002: STORE_4 0 0004: BR 0019 0007: LOADVAL 0 0009: LDC_1 2 000b: REM 000c: BRNZ 0010 000f: CALL 0 Method: Example.Exl.f 0012: BR 0015 0015: CALL 1 Method: Example.Exl.g 0018: LOADVAL 0 OOla: LDC 1 1 _ 001c: ADD OOld: STORE_4 0 001f: LOADVAL 0 0021: LDC1 100 0023: BLT 0004 0026: RET0 Tableau 3: code converti avant mise à jour des adresses de destination de saut Tout d'abord, on s'aperçoit, en comparant les adresses des instructions entre le code source et le code destination, que la taille des instructions converties est différente. Cependant, dans un premier temps, les adresses de destination de saut, apparaissant en gras et en italique ci-dessus, correspondant aux instructions de saut converties dans le code destination sont inchangées et restent identiques aux adresses de destination de saut correspondant aux instructions de saut correspondante dans le code source. Ces paramètres sont donc maintenant invalides car les instructions converties n'ont plus la même taille que les instructions correspondantes dans le code source. Par exemple, l'instruction de saut convertie BRNZ à l'adresse 000c, qui est une conversion de l'instruction de saut correspondante brtrue.s du code source, ne doit pas cibler l'adresse de destination de saut 0010, mais bien plutôt l'adresse de destination de saut 0015, où se trouve dorénavant codée l'instruction CALL , qui est l'instruction devant être ciblée par cette instruction de saut. L'étape suivante consiste alors pour le chargeur à réécrire les adresses de destination de saut dans le code destination, de sorte qu'elles pointent vers l'instruction appropriée dans le code destination. Pour déterminer les adresses de destinations devant être mises à jour dans le code destination, le chargeur peut parcourir le code source ou le code destination jusqu'à ce qu'une instruction de saut nécessitant d'être mise à jour soit identifiée. Dans ce cas, le chargeur doit convertir l'adresse de destination de saut de l'instruction de saut dans le code source en une nouvelle adresse de destination de saut appropriée dans le code destination. Ainsi, dans l'exemple, l'instruction de saut du code destination à l'adresse 0004 nécessite d'être mise à jour, de même que les instructions aux adresses 000c, 0012 et 0023. La détermination des nouvelles adresses de destination de saut dans le code destination pour ces instructions de saut va être effectuée à la volée, en ce sens qu'elle est effectuée dynamiquement et qu'aucun intermédiaire telle qu'une table de conversion n'est nécessaire. Elle va plus particulièrement être basée sur une analyse simultanée des codes source et destination, prenant en compte la taille des instructions du code source et celle des instructions converties correspondantes du code destination. Plus précisément, le chargeur est prévu pour examiner chaque instruction dans le code source en maintenant un index de source et un index de destination préalablement initialisés à zéro. Ainsi, pour chaque instruction lue dans le code source, la taille de l'instruction de source est ajoutée à l'index de source et la taille de l'instruction convertie correspondante est ajoutée à l'index de destination. De cette façon, à chaque itération, la valeur de l'index de source indique l'adresse de l'instruction courante dans le code source tandis que la valeur de l'index de destination indique l'adresse de l'instruction correspondante dans le code destination. Lorsque l'index de source est égal à l'adresse de destination de saut recherchée que l'on veut mettre à jour dans le code destination, alors l'index de destination indique une adresse d'instruction correspondante dans le code destination, correspondant à la nouvelle adresse de destination de saut qu'il convient de mettre à jour. Prenons un exemple se rapportant à la mise à jour de l'instruction de saut à l'adresse 000c dans le code destination. Le problème consiste à déterminer la nouvelle adresse de destination de saut dans le code destination correspondant à l'adresse de destination de saut 0010 de l'instruction de saut correspondante dans le code source. Dans une première étape, les index source et destination étant initialisés à zéro, on lit la première instruction 1dc.i4.0 dans le code source et on met à jour la valeur de l'index de source avec la taille de cette instruction, en l'occurrence 1 octet. Une telle instruction est toujours convertie en une instruction sur 2 octets, en l'occurrence LDC1 param . La taille de cette instruction convertie correspondante est alors ajoutée à l'index de destination pour sa mise à jour, qui prend alors la valeur 2. Puis, on passe à l'instruction suivante du code source à l'adresse 0001. Grâce aux index source et destination qui sont maintenus par le chargeur, on sait que l'instruction courante dans le code source à l'adresse 0001 a une instruction convertie correspondante à l'adresse 0002 dans le code destination. On passe alors à la deuxième instruction dans le code source. En ce qui concerne la deuxième instruction lue dans le code source stloc.0 , sa taille est de 1 octet et l'index de source est alors mis à jour en ajoutant la taille de l'instruction et prend la valeur 2. L'instruction stloc.0 peut être convertie en instructions de taille différente suivant le type de variable qui est accédée. La taille de l'instruction convertie peut ainsi varier de 2 à 3 octets. Dans cette situation, il est nécessaire d'aller lire l'instruction convertie correspondante dans le code destination, en l'occurrence STORE 4 , indiquant que c'est la variante sur 2 octets qui a été utilisée. Aussi, l'index de destination est mis à jour en ajoutant la taille de cette instruction et prend la valeur 4. De cette manière, on sait que la prochaine instruction courante à l'adresse 0002 du code source a une instruction convertie correspondante à l'adresse 0004 dans le code destination. Ce processus est réitéré jusqu'à ce que valeur de l'index de source soit égale à l'adresse de destination de saut 0010. Lorsque c'est le cas, l'index de destination contient la valeur indiquant la nouvelle adresse de destination de saut correspondante dans le code destination, en l'occurrence 001.5, qui peut alors être mise à jour dans l'instruction de saut à l'adresse 000c dans le code destination. On opère de la même façon pour la mise à jour des 30 instructions de saut converties du code destination à l'adresse 0004, 0012 et 0023, consistant à déterminer les nouvelles adresses de destination de saut correspondantes dans le code destination. On obtient pour finir le code destination final avec la mise à jour effectuée des adresses de destination de saut apparaissant en gras et en italique ci-dessous example nParams 0.nLocals 1.maxStack 2 LDC_1 0 É STORE_4 0 a BR 24 (001f) 0007: LOADVAL 0 LDC_1 2 ) C x. REM BRNZ 6 (0015) 0000: CALL 0 Method: Example.Exl.f BR 3 (0018) 0015: CALL 1 Method: Example.Exl.g 0018: LOADVAL 0 LDC_1 1 ADD 007.x: STORE_4 0 001f: LOADVAL 0 LDC_1 100 0023: BLT -31 (0007) 0026: RET 0 La figure 2 décrit un organigramme résumant les principales étapes du procédé de l'invention tel qu'il vient d'être décrit en référence à l'exemple. Ainsi, lors de l'itération sur chaque instruction dans le code source, tant que la valeur de l'index de source n'est pas égale à l'adresse de destination de saut recherchée que l'on veut mettre à jour, l'instruction courante dans le code source est lue et l'index de source est mis à jour en ajoutant la taille de l'instruction courante. La figure 2 illustre les deux alternatives qui se présentent pour la mise à jour de l'index de destination. En effet, comme on l'a vu en référence à l'exemple ci-dessus, une:instruction. dans le code source peut être convertie en une autre instruction dans le code destination d'une taille prédéfinie et connue. Dans ce cas, on a immédiatement la taille de l'instruction convertie correspondante, qui est ajoutée à l'index de destination. Cependant, il peut y avoir d'autres cas où il est nécessaire, pour mettre à jour l'index de destination, de lire l'instruction convertie correspondante dans le code destination pour connaître la taille de l'instruction convertie, voir l'exemple avec l'instruction stloc.0 ci-dessus. Puis, lorsque la valeur de l'index de source devient égale à l'adresse de destination de saut recherchée, la mise à jour de la nouvelle adresse de destination de saut est réalisée avec la valeur de l'index de destination. Bien que plus coûteux en termes de ressources de calcul que les méthodes classiques pour mettre à jour les instructions de saut converties, l'invention permet d'effectuer une telle mise à jour sans qu'il soit nécessaire d'élaborer et de stocker en mémoire des tables de conversion d'adresses, souvent consommatrices d'espace mémoire. Cette solution permet donc de libérer de la place en mémoire RAIDI lors du chargement d'un programme en langage intermédiaire et est donc particulièrement avantageuse pour les appareils portatifs contraints en mémoire. REVENDICATIONS 1. Procédé de chargement d'un code logiciel source (10) compilé en langage intermédiaire orienté objet dans un appareil numérique portatif (20) muni d'une machine virtuelle d'exécution, comprenant une étape de conversion de la suite d'instructions dudit code source pour générer un code destination (50) formé de ladite suite d'instructions converties, et une étape de mise à jour du code destination comprenant la détermination, pour chaque instruction de saut convertie, de la nouvelle adresse de destination de saut dans le code destination correspondant à l'adresse de destination de saut de l'instruction de saut correspondante dans le code source, caractérisé en ce que la détermination de la nouvelle adresse de destination de saut dans le code destination comprend une analyse simultanée des codes source et destination prenant en compte la taille des instructions du code source et celle des instructions converties correspondantes du code destination. 2. Procédé selon la revendication 1, caractérisé en ce que l'analyse simultanée des codes source et destination pour la détermination de la nouvelle adresse de destination de saut dans le code destination comprend les étapes suivantes - lire la première instruction du code source et ajouter la taille de ladite instruction à un index de source préalablement initialisé à zéro; - ajouter la taille de l'instruction convertie correspondante dans le code destination à un index de destination préalablement initialisé à zéro; - pour chaque instruction suivante du code source, lire ladite instruction et mettre à jour l'index de source en ajoutant la taille de ladite instruction et l'index de destination en ajoutant la taille de l'instruction convertie correspondante dans le code destination; - réitérer l'étape précédente jusqu'à ce que la valeur de l'index de source soit égale à l'adresse de destination de saut de l'instruction de saut correspondante dans le code source, et - mettre à jour la nouvelle adresse de destination 15 de saut dans le code destination avec la valeur de l'index de destination. 3. Procédé selon la revendication 2, caractérisé en ce que l'ajout de la taille de l'instruction convertie correspondante dans le code destination à l'index de destination comprend une étape préalable de lecture de ladite instruction dans le code destination. 4. Procédé selon l'une quelconque des revendications 1 à 3, caractérisé en ce que le code destination est un code en langage intermédiaire. 5. Procédé selon l'une quelconque des revendications 1 à 3, caractérisé en ce que le code 30 destination est un code machine exécutable par l'appareil portatif. 6. Chargeur (30) d'un code logiciel source compilé en langage intermédiaire orienté objet destiné à être chargé dans un appareil numérique portatif (20), caractérisé en ce qu'il est susceptible d'être stocké dans une mémoire non volatile de l'appareil pour la mise en oeuvre du procédé selon l'une quelconque des revendications précédentes. 7. Appareil numérique portatif comprenant une 10 mémoire non volatile mémorisant le chargeur de la revendication 6. 8. Appareil selon la revendication 7, caractérisé en ce que cet appareil est une carte à puce.