3 votes

Interprétation des tables de saut / tables de branchement

J'ai lentement repris les choses en main avec l'assemblage. Je travaille sur un Canon Rebel T1i, voici un petit extrait d'un organigramme de code que j'essaie de comprendre. A ma connaissance, je crois que la caméra a un processeur ARM v5 de 132MHz :

http://i.imgur.com/PtWC9.png

J'ai cherché dans tout Google pour essayer de comprendre le fonctionnement des tables de saut, mais j'ai beau lire, je n'arrive pas à relier les choses entre elles pour comprendre. Je comprends qu'une table de saut est similaire à une déclaration de cas, mais je ne comprends pas comment elle se déplace dans la table.

Ex : dans cet exemple il n'y a qu'une seule opération CMP, donc je ne comprends pas comment cela fonctionne exactement. Toute aide sera grandement appréciée !

7voto

dwelch Points 27195

Je ne pense pas que vous ayez assez d'informations sur la capture d'écran pour comprendre le lien avec votre question. Mais une table de saut en général...

En C, pensez à un tableau de fonctions, et vous avez initialisé chaque élément du tableau de fonctions, à un moment ultérieur, votre code prend une décision et utilise un index pour choisir une de ces fonctions. Comme vous l'avez mentionné, une déclaration de cas pourrait être implémentée de cette façon, mais ce serait l'exception et non la règle, tout dépend de la variable utilisée dans le switch et de la taille/largeur/nature des éléments dans la déclaration de cas.

Vous avez appris l'assemblage, et vous comprenez donc ce que sont les registres, comment faire des calculs avec des registres, comment stocker des choses dans des registres, etc. Le compteur de programme peut être utilisé par de nombreuses instructions comme un autre registre, la différence étant que lorsque vous y écrivez quelque chose, vous changez l'instruction qui sera exécutée ensuite.

Essayons un exemple de déclaration de cas :

switch(bob&3)
{
   case 0: ted(); break;
   case 1: joe(); break;
   case 2: jim(); bob=2; break;
   case 3: tim(); bob=7; break;
}

Ce que vous pourriez faire (mais ne feriez probablement pas) :

casetable:
     .word a
     .word b
     .word c
     .word d

    caseentry:
      ldr r1,=bob
      ldr r0,[r1]
      ldr r2,=casetable
      and r0,#3
      ldr pc,[r2,r0,lsl #2] 

    a:
      bl ted
      b caseend
    b:
      bl joe
      b caseend
    c:
      bl jim
      mov r0,#2
      ldr r1,=bob
      str r0,[r1]  
      b caseend
    d:
      bl tim
      mov r0,#7
      ldr r1,=bob
      str r0,[r1]
      b caseend

    caseend:

Ainsi, les quatre mots après l'étiquette casetable : sont les adresses où le code commence pour chacun des cas, case0 commence à a : case1 code commence à b : et ainsi de suite. Ce que nous devons faire, c'est prendre la variable utilisée par l'instruction switch et calculer mathématiquement une adresse pour l'élément dans la table. Ensuite, nous devons charger l'adresse du tableau dans le compteur du programme. L'écriture dans le compteur de programme revient à effectuer un saut.

L'échantillon C a donc été conçu intentionnellement pour rendre cela facile. D'abord, on charge le contenu de la variable bob dans r0. Les éléments de la table de saut sont des adresses de 32 bits, ou 4 octets, donc nous devons multiplier r0 par 4 pour obtenir le décalage dans la table. Un décalage vers la gauche de 2 est identique à une multiplication par 4. Et nous devons ajouter r0<<2 à l'adresse de base de la table de saut. Donc essentiellement nous calculons l'adresse_de(casetable)+((bob&3)<<2) La mémoire de lecture à cette adresse calculée et charge cette valeur dans le compteur du programme.

Avec le bras (vous avez mentionné que c'était le bras), vous pouvez faire beaucoup de choses en une seule instruction :

ldr pc,[r2,r0,lsl #2]

Charger dans le registre pc, le contenu de l'emplacement mémoire [r2+(r0<<2)]. r2 est l'adresse de casetable, et r0 est bob&3.

En gros, une table de saut se résume à calculer mathématiquement un décalage dans une table d'adresses. La table d'adresses est constituée d'adresses vers lesquelles vous voulez sauter/brancher en fonction de l'un des paramètres utilisés dans l'opération mathématique, dans mon exemple ci-dessus, Bob est cette variable. Et les adresses a,b,c,d sont les choix d'adresses parmi lesquelles je veux choisir en fonction du contenu de bob. Il y a des millions de façons amusantes et intéressantes de faire ce genre de choses, mais tout se résume à calculer au moment de l'exécution l'adresse à laquelle se brancher, et à introduire cette adresse dans le compteur du programme d'une manière qui amène le processeur particulier à effectuer ce qui est essentiellement un saut.

Notez qu'une autre façon, peut-être plus facile à lire, de calculer et de sauter dans mon exemple serait :

   mov r3,r0,lsl #2
   add r3,r2
   bx r3

Les cœurs qui supportent thumb utilisent souvent l'instruction bx avec un registre, normalement vous voyez bx lr pour revenir d'un appel de lien de branchement (sous-routine). bx lr signifie pc = lr. bx r3 signifie pc = r3.

J'espère que c'est ce que vous demandiez. Si j'ai mal compris la question, veuillez préciser.

EDITAR:

En regardant le code sur votre capture d'écran.

cmp r0,#4
addls pc,pc,r0,lsl #2

Le calcul optionnel (ADDLS add if lower or same) calcule la nouvelle valeur du compteur de programme (une table de saut est un calcul stocké dans le compteur de programme) sur la base du compteur de programme lui-même plus un offset r0 fois 4. Pour les processeurs arm, au moment de l'exécution, le compteur de programme est deux instructions en avant. donc, en mélangeant ces deux lignes de code et une partie de mon exemple :

cmp r0,#4
addls pc,pc,r0,lsl #2
ldr pc,=a
ldr pc,=b
ldr pc,=c
ldr pc,=d
...

Au moment où addls est exécuté, le compteur de programme contient l'adresse de l'instruction ldr pc,=b. Donc si r0 contient un 0 alors 0<<2 = 0, pc plus 0 se brancherait sur l'instruction ldr pc,=b puis cette instruction provoquerait un branchement sur le label b :. Si r0 contenait un 1 au moment de addls alors vous exécuteriez l'instruction ldr pc,=c suivante et ainsi de suite. Vous pouvez créer une table aussi profonde que vous le souhaitez de cette façon. Notez également que puisque l'addition est conditionnelle, si la condition ne se produit pas, vous exécuterez la première instruction après l'addls, donc peut-être voulez-vous que ce soit une branche inconditionnelle pour passer au-dessus de la table, ou une branche en arrière d'une boucle ou peut-être est-ce un nop pour que vous tombiez dans le premier saut, ou ce que j'ai fait ci-dessus est d'avoir une branche à un autre endroit. Pour comprendre ce qui se passe, vous devez donc étudier les instructions qui suivent les addls pour déterminer quelles sont les destinations possibles de la table de saut.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X