2010-05-11 4 views
8

Alors que la construction de mon assembleur pour la plate-forme x86 j'ai rencontré quelques problèmes avec le codage de l'instruction JMP:Comment un JMP relatif (x86) est-il implémenté dans un assembleur?

OPCODE INSTRUCTION SIZE 
EB cb  JMP rel8  2 
E9 cw  JMP rel16 4 (because of 0x66 16-bit prefix) 
E9 cd  JMP rel32 5 
... 

(de mon site Web d'instructions x86 favori, http://siyobik.info/index.php?module=x86&id=147)

Tous sont par rapport sauts, où la taille de chaque codage (opération + opérande) est dans la troisième colonne.

Maintenant, mon dessin original (et donc la faute à cause de cela) réservait l'espace maximum (5 octets) pour chaque instruction. L'opérande n'est pas encore connu, parce que c'est un saut vers un endroit encore inconnu. J'ai donc implémenté un mécanisme de "réécriture", qui réécrit les opérandes dans l'emplacement correct en mémoire, si l'emplacement du saut est connu, et remplit le reste avec NOP s. C'est une préoccupation assez sérieuse dans les boucles serrées.

Maintenant, mon problème est la situation suivante:

b: XXX 
c: JMP a 
e: XXX 
    ... 
    XXX 
d: JMP b 
a: XXX  (where XXX is any instruction, depending 
      on the to-be assembled program) 

Le problème est que je veux le plus petit encodage possible pour une instruction JMP (et non remplissage NOP).

je dois connaître la taille de l'instruction à c avant que je puisse calculer la distance relative entre a et b pour l'opérande à d. La même chose s'applique pour le JMP à c: il doit connaître la taille de d avant de pouvoir calculer la distance relative entre e et a.

Comment les assembleurs existants résolvent-ils ce problème, ou comment le feriez-vous?

C'est ce que je pense qui résout le problème:

encodez toutes les instructions à opcodes entre le JMP et sa cible, si cette région contient un opcode de taille variable, utilisez la taille maximale , par exemple 5 pour un JMP. Ensuite, codez le JMP relatif à sa cible, en choisissant la plus petite taille d'encodage possible (3, 4 ou 5) et calculez la distance. Si un opcode de taille variable est codé, changez tous les opérandes absolus avant, et toutes les instructions relatives qui saute cette instruction codée: ils sont recodés quand leur opérande change pour choisir la plus petite taille possible. Cette méthode est garantie pour se terminer, car les opcodes de taille variable peuvent seulement rétrécir (parce qu'il utilise leur taille maximale).

Je me demande, peut-être ce est une solution sur-ingénierie, c'est la raison pour laquelle je pose cette question.

+0

+1 pour la belle lien de documentation asm –

Répondre

1

est ici une approche que je l'ai utilisé qui peut sembler inefficace, mais se révèle ne pas être pour la plupart du code de la vie réelle (pseudo-code):

IP := 0; 
do 
{ 
    done = true; 
    while (IP < length) 
    { 
    if Instr[IP] is jump 
     if backwards 
     { Target known 
      Encode short/long as needed } 
     else 
     { Target unknown 
      if (!marked as needing long encoding) // see below 
      Encode short 
      Record location for fixup } 
    IP++; 
    } 
    foreach Fixup do 
    if Jump > short 
     Mark Jump location as requiring long encoding 
     PC := FixupLocation; // restart at instruction that needs size change 
     done = false; 
     break; // out of foreach fixup 
    else 
     encode jump 
} while (!done); 
+0

Neat! Bien que, mais vous ne pouvez pas le savoir, mon assembleur et mon compilateur fonctionnent en parallèle, il est donc possible que le code soit inséré entre une cible et un saut relatif vers l'arrière. Mais le deux-passes en boucle est une très bonne approche, merci. (Aussi PC = IP dans la boucle Fixup, je suppose?) – Pindatjuh

+1

Oh ouais, désolé - PC = IP. –

+0

En fait, je me rappelle juste qu'il y a une petite complication avec ceci: Quand vous retournez et redimensionnez un saut, vous devez également prendre en compte tous les sauts avant courts sur cet endroit que vous êtes sur le point de développer et qui ne peuvent plus atteindre leurs cibles. –

3

Dans le premier passage, vous aurez une très bonne approximation à laquelle jmp code à utiliser en utilisant un comptage d'octets pessimistes pour toutes les instructions de saut.

Lors de la deuxième passe, vous pouvez remplir les sauts avec l'opcode pessimiste choisi. Très peu de sauts pourraient alors être réécrits pour utiliser un octet ou deux de moins, seulement ceux qui étaient très proches du seuil de saut 8/16 bits ou 16/32 octets à l'origine.Comme les candidats sont tous des sauts de plusieurs octets, ils sont moins susceptibles d'être dans des situations de boucles critiques, de sorte que vous trouverez probablement que d'autres passes offrent peu ou pas d'avantages par rapport à une solution en deux passes.

+0

de Grande réponse : mais pourquoi la limite de 128 octets (entre 8/16 bits) est-elle moins susceptible d'être dans des situations de boucles critiques? Je peux imaginer une situation de boucle critique de 128 octets exactement, qui s'exécutera plus vite qu'avec un saut de 16 bits. Ou est-ce une optimisation prématurée? – Pindatjuh

+1

Eh bien ce que je veux dire par une boucle critique est celui dans lequel le test et le saut de la boucle est une partie importante du code de la boucle. 128 octets est très long pour une telle boucle, les boucles les plus critiques seront quelques octets et tout saut hors d'une boucle est susceptible d'être également petit. –

Questions connexes