web-dev-qa-db-fra.com

Que sont SP (pile) et LR dans ARM?

Je lis les définitions encore et encore et je ne comprends toujours pas ce que sont SP et LR dans ARM? Je comprends PC (il montre l'adresse de la prochaine instruction), SP = et LR sont probablement similaires, mais je ne comprends pas ce que c'est. Pourriez-vous m'aider, s'il vous plaît?

edit: si vous pouviez l'expliquer avec des exemples, ce serait superbe.

edit: a finalement trouvé à quoi sert le LR, mais ne comprend toujours pas ce que SP est pour.

68
good_evening

LR est registre de liaison utilisé pour conserver l'adresse de retour pour un appel de fonction.

SP est un pointeur de pile. La pile est généralement utilisée pour contenir des variables "automatiques" et un contexte/paramètres lors d'appels de fonction. Conceptuellement, vous pouvez considérer la "pile" comme un endroit où vous "empilez" vos données. Vous gardez "empiler" une donnée sur une autre et le pointeur de pile vous indique à quel point votre "pile" de données est "haute". Vous pouvez supprimer les données du "sommet" de la "pile" et les raccourcir.

De la référence d'architecture ARM:

SP, le pointeur de pile

Le registre R13 est utilisé comme pointeur sur la pile active.

En code Thumb, la plupart des instructions ne peuvent pas accéder à SP. Les seules instructions pouvant accéder à SP sont celles conçues pour utiliser SP comme pointeur de pile. L'utilisation de SP pour Toute utilisation autre qu'un pointeur de pile est obsolète. Remarque Utiliser SP pour un usage autre que celui d'un pointeur de pile est susceptible de briser les exigences des systèmes d'exploitation, des débogueurs et qu'ils fonctionnent mal.

LR, le registre de liens

Le registre R14 est utilisé pour stocker l'adresse de retour d'un sous-programme. À d'autres moments, LR peut être utilisé à d'autres fins.

Lorsqu'une instruction BL ou BLX effectue un appel de sous-routine, LR est défini sur l'adresse de retour de sous-routine. Pour effectuer un retour de sous-programme, recopiez LR dans le compteur de programme. Cela se fait généralement de deux manières, après avoir entré le sous-programme avec une instruction BL ou BLX:

• Revenir avec une instruction BX LR.

• Sur l'entrée du sous-programme, enregistrez LR dans la pile avec une instruction de la forme: Appuyez sur {, LR} et utilisez une instruction correspondante pour renvoyer: POP {, PC} ...

Ce lien donne un exemple de sous-routine triviale.

Voici un exemple de la façon dont les registres sont sauvegardés sur la pile avant un appel puis remontés pour restaurer leur contenu.

84
Guy Sirton

SP est le registre de pile, un raccourci pour taper r13. LR est le lien enregistrer un raccourci pour r14. Et PC est le programme contrer un raccourci pour taper r15.

Lorsque vous effectuez un appel, appelé instruction de liaison de branche, bl, l'adresse de retour est placée dans r14, le registre de liaison. le compteur de programme pc est remplacé par l'adresse vers laquelle vous vous dirigez.

Il existe quelques pointeurs de pile dans les cœurs traditionnels ARM (la série cortex-m étant une exception) lorsque vous frappez une interruption, par exemple, vous utilisez une pile différente de celle utilisée au premier plan, vous n'avez pas à changer. votre code utilise simplement sp ou r13 comme d'habitude le matériel a effectué le changement pour vous et utilise celui qui convient pour décoder les instructions.

Le jeu d'instructions traditionnel ARM (pas de pouce) vous donne la liberté d'utiliser la pile dans une liste d'adresses grandioses grandioses ou plus élevées. les compilateurs et la plupart des gens définissent le pointeur de pile haut et le font passer des adresses hautes aux adresses basses. Par exemple, vous avez peut-être une mémoire RAM allant de 0x20000000 à 0x20008000 et que vous définissez votre script pour créer un programme exécutant/exploitant 0x20000000 et définissant votre pointeur de pile sur 0x20008000 dans votre code de démarrage, au moins le pointeur de pile système/utilisateur, vous devez le diviser la mémoire pour les autres piles si vous en avez besoin/utilisez-les.

La pile n'est que de la mémoire. Les processeurs ont normalement des instructions spéciales de lecture/écriture en mémoire, basées sur PC et, pour certaines, sur pile. Les piles au minimum sont généralement appelées Push et Pop mais ne doivent pas l'être (comme avec les instructions de bras traditionnelles).

Si vous allez sur http://github.com/lsasim , j'ai créé un processeur d'enseignement et un tutoriel sur la langue d'assemblage. Quelque part, je discute des piles. Ce n’est PAS un processeur à bras, mais l’histoire est la même; il devrait traduire directement ce que vous essayez de comprendre sur le bras ou la plupart des autres processeurs.

Supposons, par exemple, que votre programme ait 20 variables dont vous avez besoin, mais que 16 registres moins au moins trois d'entre eux (sp, lr, pc) ont une fonction particulière. Vous allez devoir garder certaines de vos variables dans le ram. Disons que r5 contient une variable que vous utilisez assez souvent pour que vous ne vouliez pas la garder en mémoire vive, mais il y a une section de code dans laquelle vous avez vraiment besoin d'un autre registre pour faire quelque chose et où r5 n'est pas utilisé, vous pouvez enregistrer r5 sur la pile avec un effort minimal pendant que vous réutilisez r5 pour autre chose, puis plus tard, restaurez-la facilement.

Syntaxe de bras classique (enfin pas tout au début):

...
stmdb r13!,{r5}
...temporarily use r5 for something else...
ldmia r13!,{r5}
...

stm is store multiple, vous pouvez enregistrer plusieurs registres à la fois, en une seule instruction.

db signifie décrémenter avant, il s’agit d’une pile déplacée vers le bas des adresses élevées aux adresses inférieures.

Vous pouvez utiliser r13 ou sp ici pour indiquer le pointeur de pile. Cette instruction particulière n'est pas limitée aux opérations de pile, elle peut être utilisée à d'autres fins.

Le ! signifie que vous devez mettre à jour le registre r13 avec la nouvelle adresse une fois celle-ci terminée; ici encore, stm peut être utilisé pour des opérations autres que la pile, de sorte que vous ne souhaitiez peut-être pas modifier le registre des adresses de base, laissez le! dans ce cas.

Puis, entre parenthèses {}, dressez la liste des registres que vous souhaitez sauvegarder, séparés par des virgules.

ldmia est l'inverse, ldm signifie charge multiple. ia signifie incrémenter après et le reste est identique à stm

Donc, si votre pointeur de pile était à 0x20008000 lorsque vous appuyez sur l'instruction stmdb, vu qu'il y a un registre de 32 bits dans la liste, il sera décrémenté avant qu'il ne l'utilise la valeur dans r13 afin que 0x20007FFC soit écrit, puis écrit r5 dans 0x20007FFC et enregistre la valeur 0x20007FFC dans r13. Plus tard, en supposant que vous n’ayez aucun bogue lorsque vous obtenez l’instruction ldmia r13 qui contient 0x20007FFC, il n’ya qu’un seul registre dans la liste r5. Donc, il lit la mémoire à 0x20007FFC met cette valeur dans r5, ia signifie incrémentation après donc 0x20007FFC incrémente une taille de registre à 0x20008000 et le symbole! signifie écrire ce nombre dans r13 pour compléter l’instruction.

Pourquoi voudriez-vous utiliser la pile au lieu d'un simple emplacement mémoire? Bien que la beauté de ce qui précède est que r13 peut être n'importe où il pourrait être 0x20007654 lorsque vous exécutez ce code ou 0x20002000 ou autre et que le code fonctionne toujours, encore mieux si vous utilisez ce code dans une boucle ou avec la récursivité, cela fonctionne et pour chaque niveau de la récursion, vous sauvegardez une nouvelle copie de r5, vous pourriez avoir 30 copies sauvegardées en fonction de votre position dans la boucle. et en se déroulant, il remet toutes les copies comme vous le souhaitez. avec un seul emplacement de mémoire fixe qui ne fonctionne pas. Cela se traduit directement en code C à titre d'exemple:

void myfun ( void )
{
   int somedata;
}

Dans un programme C comme celui où la variable somedata réside dans la pile, si vous appelez myfun de manière récursive, vous obtiendrez plusieurs copies de la valeur de somedata en fonction de la profondeur de la récursion. De plus, comme cette variable n’est utilisée que dans la fonction et n’est pas utilisée ailleurs, vous ne voudrez peut-être pas graver une quantité de mémoire système pour cette variable pendant toute la durée du programme. Vous ne voulez que ces octets lorsque vous utilisez cette fonction pas dans cette fonction. c'est à quoi sert une pile.

Une variable globale ne serait pas trouvée sur la pile.

Retourner...

Supposons que vous souhaitiez implémenter et appeler cette fonction, vous auriez un code/une fonction dans lequel vous vous trouvez lorsque vous appelez la fonction myfun. La fonction myfun veut utiliser r5 et r6 lorsqu'elle opère sur quelque chose, mais elle ne veut pas jeter le nom de quelqu'un qui l'appelait à l'aide de r5 et r6 pendant la durée de myfun (), vous voudrez donc sauvegarder ces registres sur la pile. De même, si vous regardez l'instruction de liaison de branche (bl) et le registre de liaison lr (r14), il n'y a qu'un seul registre de liaison. Si vous appelez une fonction à partir d'une fonction, vous devrez enregistrer le registre de liaison à chaque appel, sinon vous ne pourrez pas revenir. .

...
bl myfun
    <--- the return from my fun returns here
...


myfun:
stmdb sp!,{r5,r6,lr}
sub sp,#4 <--- make room for the somedata variable
...
some code here that uses r5 and r6
bl more_fun <-- this modifies lr, if we didnt save lr we wouldnt be able to return from myfun
   <---- more_fun() returns here
...
add sp,#4 <-- take back the stack memory we allocated for the somedata variable
ldmia sp!,{r5,r6,lr}
mov pc,lr <---- return to whomever called myfun.

J'espère donc que vous pourrez voir à la fois l'utilisation de la pile et le registre de liens. D'autres processeurs font le même genre de choses d'une manière différente. Par exemple, certains vont mettre la valeur de retour sur la pile et lorsque vous exécutez la fonction de retour, ils savent où retourner en extrayant une valeur de la pile. Les compilateurs C/C++, etc. auront normalement une "convention d'appel" ou une interface d'application (ABI et EABI sont les noms de ceux définis par ARM). si chaque fonction suit la convention d'appel, met les paramètres qu'elle transmet aux fonctions appelées dans les registres de droite ou sur la pile, conformément à la convention. Et chaque fonction suit les règles relatives aux registres dont elle n'a pas besoin pour préserver le contenu et aux registres dont elle dispose pour préserver le contenu. Vous pouvez ainsi demander aux fonctions d’appeler des fonctions, d’effectuer des récursions et toutes sortes de choses, tant que la pile ne va pas si loin qu'elle se trouve dans la mémoire utilisée pour les globals et le tas, vous pouvez appeler des fonctions et en revenir toute la journée. L'implémentation ci-dessus de myfun est très similaire à ce que vous verriez un compilateur.

ARM a maintenant beaucoup de cœurs et quelques instructions définissent la série cortex-m fonctionne un peu différemment pour ne pas avoir un tas de modes et des pointeurs de pile différents. Et lorsque vous exécutez des instructions de pouce en mode pouce, vous utilisez les instructions Push et Pop qui ne vous donnent pas la liberté d'utiliser un registre comme stm. Elles utilisent uniquement r13 (sp) et vous ne pouvez pas enregistrer tous les registres uniquement un sous-ensemble spécifique de ceux-ci. les assembleurs de bras populaires vous permettent d'utiliser

Push {r5,r6}
...
pop {r5,r6}

code bras et code pouce. Pour le code de bras, il code les fichiers stmdb et ldmia appropriés. (En mode pouce, vous n'avez pas non plus le choix de quand et où vous utilisez db, décrémenter avant et ia, incrémenter après).

Non, vous ne devez absolument pas utiliser les mêmes registres et vous ne devez pas associer le même nombre de registres.

Push {r5,r6,r7}
...
pop {r2,r3}
...
pop {r1}

en supposant qu'il n'y a pas d'autres modifications de pointeur de pile entre ces instructions si vous vous rappelez que le sp va être décrémenté 12 octets pour le Push permettent de dire de 0x1000 à 0x0FF4, r5 sera écrit dans 0xFF4, r6 à 0xFF8 et r7 à 0xFFC dans la pile le pointeur passera à 0x0FF4. la première pop prendra la valeur 0x0FF4 et la mettra dans R2 puis la valeur 0x0FF8 et la mettra dans r3, le pointeur de pile obtiendra la valeur 0x0FFC. plus tard au dernier pop, sp est 0x0FFC qui est lu et la valeur placée dans r1, le pointeur de pile obtient alors la valeur 0x1000, où il a commencé.

Le ARM ARM, ARM Manuel de référence architectural (infocenter.arm.com, manuels de référence, recherchez-le pour ARMv5 et téléchargez-le. Il s'agit du traditionnel ARM ARM avec ARM et les instructions du pouce) contient un pseudo-code pour les instructions ldm et stm ARM afin d'obtenir une image complète de la manière dont elles sont utilisées. De même, tout le livre traite du bras et de la façon de le programmer. Au début, le chapitre sur les modèles pour programmeurs vous guide dans tous les registres, dans tous les modes, etc.

Si vous programmez un processeur ARM, vous devez commencer par déterminer (le fournisseur de puces devrait vous le dire, ARM ne fabrique pas de puces, il crée des cœurs que les vendeurs de puces placent dans leurs puces). Ensuite, allez sur le site Web de arm et recherchez le ARM ARM de cette famille, ainsi que le TRM (manuel de référence technique) du noyau spécifique, y compris la révision, si le fournisseur en a fourni (r2p0 signifie révision 2.0 (deux)). point zéro, 2p0)), même s’il existe une version plus récente, utilisez le manuel qui va avec celui utilisé par le fournisseur dans sa conception. Chaque cœur ne prend pas en charge chaque instruction ou mode. Le TRM vous indique les modes et instructions pris en charge. Le ARM ARM jette une couverture sur les fonctionnalités de toute la famille de processeurs dans laquelle ce noyau est installé. Notez que le ARM7TDMI est un ARMv4 PAS un ARMv7 De même, le ARM9 n'est pas un ARMv9. ARMvNUMBER est le nom de famille ARM7, ARM11 sans v est le nom principal. Les nouveaux noyaux portent des noms tels que Cortex et mpcore, au lieu d’ArmNUMBER, ce qui réduit la confusion. Bien sûr, ils ont dû ajouter la confusion en fabriquant un ARMv7-m (cortex-MNUMBER) et un ARMv7-a (Cortex-ANUMBER) qui sont des familles très différentes, l’un pour les charges lourdes, les ordinateurs de bureau, les ordinateurs portables, etc. pour les microcontrôleurs, les horloges et les lumières clignotantes de la cafetière et ainsi de suite. google beagleboard (Cortex-A) et le tableau de découverte de la ligne de valeur stm32 (Cortex-M) pour mieux comprendre les différences. Ou même le tableau open-rd.org qui utilise plusieurs cœurs à plus d’un gigahertz ou le nouveau Tegra 2 de Nvidia, super-écailleur, même cœur, multi-gigahertz. Un cortex-m freine à peine la barrière des 100 MHz et sa mémoire est mesurée en kilo-octets bien qu'il fonctionne probablement pendant plusieurs mois si vous le vouliez moins encombré par un cortex.

désolé pour le très long post, espérons que c'est utile.

42
old_timer