0
votes

Comment faire en sorte que gcc génère une pile dans un environnement bare-metal?

Lorsque j'utilise GCC pour le développement du système d'exploitation ARM, je ne peux pas utiliser la variable locale car la pile n'a pas été initialisée, alors comment dire au compilateur d'initialiser SP?


5 commentaires

Je pense que vous écririez normalement un asm à la main pour initialiser des choses, y compris la pile, avant d'appeler ou de passer à une fonction générée par le compilateur. Ou si votre "noyau" a des métadonnées lues par un chargeur, il pourrait être en mesure de spécifier une pile? IDK, cela dépendra du chargeur de démarrage que vous utilisez.


Normalement, vous ne le dites pas au compilateur. Vous dites à l'éditeur de liens. Vous créez un lien avec un morceau de code d'assemblage qui initialise la pile et tout ce dont vous avez besoin, puis passe à votre code. Si vous voulez dire au compilateur, vous devez écrire l'assembly en ligne comme la première chose que fait votre programme.


@PeterCordes mais si j'utilise asm ("mov sp, # 0x8000") ;, le code généré par le compilateur utilisera push avant l'instruction, comment puis-je faire en sorte que le compilateur le fasse en premier?


@AlanJian Veuillez montrer le code en question ainsi que les options exactes avec lesquelles vous compilez. Normalement, __attribute__((naked)) peut être utilisé mais cela dépend vraiment de votre cas d'utilisation.


Vous avez mal compris ce que j'ai dit: vous écrivez du code en langage assembleur dans un fichier .S séparé qui configure la machine purement asm puis appelle votre C comme bl main , comme n.'pronouns'm. m'a dit. Pas une instruction asm à l'intérieur de votre C. Cela ne peut pas appeler votre C car il est déjà dans une fonction C, comme vous l'avez souligné. (Ou comme fuz l'a dit, vous pouvez utiliser __attribute__((naked)) ou une instruction asm("") à portée globale, mais AFAIK ceux-ci n'ont aucun avantage particulier par rapport à un fichier séparé pour votre asm.)


3 Réponses :


1
votes

Mon expérience est avec le Cortex-M, et comme @ n-pronoms-m l'a dit, c'est l'éditeur de liens, pas le compilateur ou l'assembleur, qui "met en place" la pile. Tout ce qui est nécessaire est de placer la valeur initiale du pointeur de pile à l'emplacement 0x0 dans la mémoire du programme. Il s'agit généralement de (l'adresse RAM la plus élevée + 4). Étant donné que différents processeurs ont différentes quantités de RAM, l'adresse appropriée dépend du processeur et est généralement un littéral dans le fichier de l'éditeur de liens.


8 commentaires

Pourriez-vous me montrer un exemple de le faire dans l'éditeur de liens sur Cortex-M? Je me demande ce que fait le segment de valeur SP initial dans la table vectorielle.


Que signifie l'adresse RAM la plus élevée? Par exemple, les différents modèles de Raspberry Pi ont différents RAM comme pi4 2g, 4g et 8g, mais leur processeur est le même, donc l'adresse RAM la plus élevée signifie-t-elle la taille du cache?


@AlanJian Je peux, mais j'ai besoin de connaître le modèle exact de micro-contrôleur pour lequel vous programmez pour le faire. Chacun est légèrement différent. Notez que le Raspberry Pi est un Cortex A, pas un Cortex M, il fonctionne donc très différemment de cela.


J'utilise Raspberry Pi 0 avec Arm 6 (Arm1176JZF-S), quelle est l'adresse RAM la plus élevée?


Le fournisseur de puces ou le concepteur de cartes implémente la quantité de mémoire ARM n'est pas impliquée. Le pi zéro a actuellement 512 Mo de dram, ce que vous devriez déjà savoir. Il y a un très bon forum bare metal sur le site pi avec beaucoup de bons liens et exemples. Le document Broadcom indique qu'une partie du bélier est pour le bras et d'autres pour le GPU mais vous pouvez l'ajuster vous-même dans une certaine mesure. Pour un pi zéro qui copie normalement votre programme vers 0x8000 dans l'espace des bras, j'ai tendance à y placer mon pointeur de pile au lieu du haut de la mémoire


Si votre question (vous devrez écrire une meilleure question la prochaine fois avec plus de détails) concerne le pi zéro, ce n'est pas un cortex-m. Cette réponse est spécifiquement pour les noyaux cortex-m et non pour arm11 (armv6) dans le pi zéro. Cela ne fonctionnera pas pour un pi zéro. Je peux annuler ma réponse, ou en écrire une nouvelle (comme Elliot si vous posez vraiment des questions sur le pi zéro), mais il y a des dizaines à des centaines de milliers d'exemples pour armv4t sur des cœurs avec une table d'exceptions comme armv6, donc pas vraiment un besoin pour la question ici ni une réponse.


au lieu de la table vectorielle, vous écrivez simplement la valeur dans le pointeur de pile, mais il y a plusieurs pointeurs de pile presque un pour chaque mode de processeur, donc quel que soit le mode que vous devrez utiliser, vous voudrez définir le pointeur de pile dans ce cas, même réponse que ci-dessus, commencez en haut de la mémoire et travaillez vers le bas, choisissez une taille pour chacun et écrivez simplement ces valeurs dans le pointeur de pile approprié.


@old_timer C'est vraiment dommage, votre réponse était plutôt bonne. Peut-être envisager de le publier seul sur une nouvelle question qui vient d'être posée pour le publier?



0
votes

Votre question est déroutante puisque vous ne spécifiez pas la cible, il existe différentes réponses pour les différentes saveurs de l'architecture ARM. Mais indépendamment de cela, gcc n'a rien à voir avec cela. Gcc est un compilateur C et en tant que tel, vous avez besoin d'un bootstrap écrit dans un autre langage idéalement (sinon, il semble mauvais et vous combattez de toute façon un problème de poule et d'oeuf). Généralement réalisé en langage d'assemblage.

Pour l'armv4t jusqu'aux cœurs armv7-a, vous avez différents modes de processeur, utilisateur, système, superviseur, etc. Lorsque vous regardez le manuel de référence d'architecture, vous voyez que le pointeur de pile est mis en banque, un pour chaque mode ou au moins plusieurs des modes ont leur un plus un peu de partage. Ce qui signifie que vous devez avoir un moyen d'accéder à ce registre. Pour ces cœurs, comment cela fonctionne, vous devez changer de mode, définir le mode de commutation du pointeur de pile, définir le pointeur de pile, jusqu'à ce que vous ayez tous ceux que vous allez utiliser (voir les dizaines à des centaines de milliers d'exemples sur Internet en ce qui concerne comment faire). Et puis revenez souvent en mode superviseur pour ensuite démarrer dans l'application / le noyau comme vous voulez l'appeler.

Ensuite, avec l'armv8-a et je pense que l'armv7-a aussi vous avez un mode hyperviseur qui est différent. Et certainement armv8-a qui est le noyau 64 bits (a un noyau compatible armv7-a à l'intérieur pour l'exécution aarch32).

Tout ce qui précède, mais vous devez définir le pointeur de pile dans votre code

.word 0x20001000
.word main
.word handler
.word handler
...

ou quelque chose de ce genre. Sur les premiers Pis, c'est le genre de chose que vous pourriez faire car ce chargeur mettrait votre kernel.img à 0x8000, sauf indication contraire, donc de juste en dessous du point d'entrée à juste au-dessus des ATAG est l'espace libre et après le démarrage si vous utilisez le Les entrées ATAG, vous êtes libre jusqu'à la table des exceptions (que vous devez configurer, le moyen le plus simple est de laisser les outils travailler pour vous et de générer les adresses, puis de les copier simplement à leur emplacement approprié. Ce genre de chose.

reset:
    bl main
hang: b hang

L'armv8-m a une table d'exceptions mais les exceptions sont espacées comme indiqué dans la documentation ARM.

L'adresse ci-dessus bien connue documentée par ARM est un point d'entrée, le code commence à s'exécuter là-bas, vous devez donc y placer les instructions, puis si c'est le gestionnaire de réinitialisation qui est normalement où vous ajouteriez du code pour configurer le pointeur de pile, copiez. data, zéro .bss et tout autre bootstrapping nécessaire avant que le code C puisse être entré.

Les cortex-ms que sont armv6-m, armv7-m et armv8-m (jusqu'ici compatibles avec l'un ou l'autre) utilisent une table vectorielle. Ce qui signifie que les adresses bien connues sont des vecteurs, des adresses du gestionnaire, pas des instructions, vous feriez donc quelque chose comme ça

reset:
    ldr sp,=0x8000
    bl main
hang: b hang

Comme documenté par ARM, la table des vecteurs cortex-m a une entrée pour l'initialisation du pointeur de pile, vous n'avez donc pas à ajouter de code, il suffit de mettre l'adresse là. Lors de la réinitialisation, la logique lit à partir de 0x00000000 place cette valeur dans le pointeur de pile, lit à partir de 0x00000004 vérifie et supprime le lsbit et commence l'exécution à cette adresse (lsbit doit être défini dans la table vectorielle, veuillez ne pas effectuer la réinitialisation + 1, utiliser correctement les outils).

Remarque _start n'est pas réellement nécessaire, c'est juste une distraction, il n'y a pas de chargeur qui a besoin de savoir ce qu'est un point d'entrée, de même vous créez idéalement votre propre script de bootstrap et d'éditeur de liens, donc il n'y a pas besoin de _start si vous ne le mettez pas dans votre script de l'éditeur de liens. Juste une habitude plus que tout pour l'inclure, enregistre des questions plus tard.

Lorsque vous lisez le manuel de référence d'architecture, l'un d'entre eux, vous remarquez comment la description de l'instruction stm / push décrémente d'abord puis stocke, donc si vous définissez 0x20001000, la première chose envoyée est à l'adresse 0x20000FFC, pas 0x20001000, pas nécessairement true pour les non-ARM, donc, comme toujours, obtenez et lisez d'abord les documents, puis commencez à coder.

Vous, le programmeur bare-metal, êtes entièrement responsable de la carte mémoire au sein de l'implémentation par le fournisseur de puces. Donc, s'il y a 64 Ko de RAM de 0x20000000 à 0x20010000, vous décidez comment le découper. Il est super facile de simplement utiliser la pile traditionnelle qui descend du haut, les données en bas, le tas au milieu, mais pourquoi auriez-vous un tas sur un mcu si c'est un mcu dont vous parlez (vous l'avez fait ne pas préciser). Donc, pour un cortex-m de RAM de 64 Ko, vous voudrez probablement simplement mettre 0x20010000 dans la première entrée de la table vectorielle, question d'initialisation du pointeur de pile terminée. Certaines personnes aiment beaucoup trop compliquer les scripts de l'éditeur de liens en général et, pour une raison quelconque, je ne peux pas comprendre, définissez la pile dans le script de l'éditeur de liens. Dans ce cas, vous utilisez simplement une variable définie dans le script de l'éditeur de liens pour indiquer le haut de la pile et vous l'utilisez dans votre table vectorielle pour un cortex-m ou dans le code d'amorçage pour un ARM de taille normale.

Le fait d'être entièrement responsable de l'espace mémoire dans les limites de l'implémentation de la puce signifie également que vous configurez le script de l'éditeur de liens pour qu'il corresponde, vous devez connaître les adresses d'exception ou de table vectorielle bien connues telles que documentées dans les documents que vous avez déjà lus à ce stade Oui?

Pour un cortex-m peut-être quelque chose comme ça

MEMORY
{
    ram : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > ram
    .rodata : { *(.rodata*) } > ram
    .bss : { *(.bss*) } > ram
    .data : { *(.data*) } > ram
}

Pour aa Pi Zero peut-être quelque chose comme ça:

MEMORY
{
    /* rom : ORIGIN = 0x08000000, LENGTH = 0x1000 *//*AXIM*/
    rom : ORIGIN = 0x00200000, LENGTH = 0x1000 /*ITCM*/
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text   : { *(.text*)   } > rom
    .rodata : { *(.rodata*) } > rom
    .bss    : { *(.bss*)    } > ram
}

et vous pouvez le compliquer à partir de là.

Le pointeur de pile est la partie facile du bootstrap que vous venez de mettre un nombre que vous avez choisi lorsque vous avez conçu votre carte mémoire. L'initialisation de .data et .bss est plus compliquée, bien que pour un | Pi Zero si vous savez ce que vous faites, le script de l'éditeur de liens peut être comme ci-dessus et le bootstrap peut être aussi simple

.thumb

.globl _start
_start:
.word 0x20001000
.word reset
.word loop
.word loop
.word loop

.thumb_func
reset:
    bl main
    b .
.thumb_func
loop:
    b .

Si vous ne changez pas de mode et n'utilisez pas argc / argv. Vous pouvez le compliquer à partir de là.

Pour un cortex-m, vous pouvez le rendre plus simple que ça

.globl _start
_start:
    ldr pc,reset_handler
    ldr pc,undefined_handler
    ldr pc,swi_handler
    ldr pc,prefetch_handler
    ldr pc,data_handler
    ldr pc,unused_handler
    ldr pc,irq_handler
    ldr pc,fiq_handler
reset_handler:      .word reset
undefined_handler:  .word hang
swi_handler:        .word hang
prefetch_handler:   .word hang
data_handler:       .word hang
unused_handler:     .word hang
irq_handler:        .word irq
fiq_handler:        .word hang

reset:
    mov r0,#0x8000
    mov r1,#0x0000
    ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
    stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}
    ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
    stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}


    ;@ (PSR_IRQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
    mov r0,#0xD2
    msr cpsr_c,r0
    mov sp,#0x8000

    ;@ (PSR_FIQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
    mov r0,#0xD1
    msr cpsr_c,r0
    mov sp,#0x4000

    ;@ (PSR_SVC_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
    mov r0,#0xD3
    msr cpsr_c,r0
    mov sp,#0x8000000

    ;@ SVC MODE, IRQ ENABLED, FIQ DIS
    ;@mov r0,#0x53
    ;@msr cpsr_c, r0

Ou si vous n'utilisez pas .data ou .bss ou si vous n'en avez pas besoin initialisé, vous pouvez techniquement le faire:

reset:
    mov sp,=0x8000

Mais la plupart des gens à part moi comptent sur .bss pour être zéro et .data pour être initialisé. Vous ne pouvez pas non plus revenir de main, ce qui convient parfaitement à un système nu comme un mcu si la conception de votre logiciel est pilotée par des événements et qu'il n'est pas nécessaire de passer au premier plan après la configuration. La plupart des gens pensent que vous ne pouvez pas revenir du principal.

gcc n'a rien à voir avec tout cela, gcc est juste un compilateur qu'il ne peut pas l'assembler ne peut pas le lier, il ne peut même pas compiler, gcc est un frontal qui appelle d'autres outils qui font ces tâches un analyseur un compilateur un assembleur et un éditeur de liens à moins dit de ne pas le faire. L'analyseur et le compilateur font partie de gcc. L'assembleur et l'éditeur de liens font partie d'un package différent appelé binutils qui contient de nombreux utilitaires binaires et inclut également l'assembleur gnu ou gas. Il inclut également l'éditeur de liens gnu. Les langages d'assemblage sont spécifiques à un assembleur et non à la cible, les scripts de l'éditeur de liens sont spécifiques à l'éditeur de liens et l'assemblage en ligne est spécifique au compilateur, de sorte que ces éléments ne sont pas supposés être portés d'une chaîne d'outils à une autre. Il n'est généralement pas judicieux d'utiliser l'assemblage en ligne, vous devez être assez désespéré, mieux utiliser l'assemblage réel ou pas du tout, cela dépend du vrai problème. Mais oui avec gnu, vous pouvez intégrer le bootstrap si vous en ressentez vraiment le besoin.

S'il s'agit d'une question Raspberry Pi, le chargeur de démarrage GPU copie le programme ARM dans la RAM pour vous afin que le tout soit en RAM, ce qui le rend tellement plus facile par rapport à d'autres bare metal. Pour un mcu bien que la logique démarre simplement en utilisant la solution documentée, vous êtes responsable de l'initialisation de la RAM, donc si vous avez des .data ou .bss que vous souhaitez initialiser, vous devez le faire dans le bootstrap. Les informations doivent être en RAM non volatile, vous utilisez donc l'éditeur de liens pour faire deux choses: mettre ces informations dans l'espace non volatile (rom / flash) et lui dire où vous allez les avoir dans la RAM, si vous utilisez les outils correctement, l'éditeur de liens vous dira où il a mis chaque chose dans flash / ram et vous pouvez ensuite utiliser par programme des variables init ces espaces. (avant d'appeler main bien sûr).

Il existe une relation très intime entre le bootstrap et le script de l'éditeur de liens pour cette raison pour une plate-forme où vous êtes responsable de .data et .bss (ainsi que d'autres complications que vous créez et que vous utilisez l'éditeur de liens pour résoudre). Certes, avec gnu, lorsque vous utilisez la conception de votre carte mémoire pour spécifier l'emplacement des sections .text, .data, .bss, vous créez des variables dans le script de l'éditeur de liens pour connaître le point de départ, le point final et / ou la taille, et ces variables sont utilisé par le bootstrap pour copier / initier ces sections. Comme asm et le script de l'éditeur de liens dépendent de l'outil, ils ne sont pas censés être portables, vous devez donc le refaire éventuellement pour chaque outil (où le C est plus portable si vous n'utilisez aucun asm en ligne et aucun pragmas, etc. de toute façon)) donc plus la solution est simple, moins vous devez porter de code si vous souhaitez essayer l'application sur différents outils, souhaitez prendre en charge différents outils pour que l'utilisateur final utilise l'application, etc.

Les cœurs les plus récents avec aarch64 sont assez compliqués en général, mais surtout si vous voulez choisir un mode spécifique, il y a du code bootstrap très délicat que vous devrez peut-être écrire. La bonne chose est que pour les registres en banque, vous pouvez y accéder directement à partir de modes privilégiés plus élevés et vous n'avez pas à faire la chose de commutateur de mode comme l'armv4t et autres. Pas beaucoup d'économies comme les niveaux d'exécution, tout ce que vous devez savoir, configurer et maintenir est assez détaillé. Y compris les piles pour chaque couche d'exécution et pour les applications lorsque vous les lancez si vous créez un système d'exploitation.


0 commentaires

1
votes

Ceci est une variation du code que j'utilise au niveau mondial dans mon code C bare metal, aarch64, Pi3. Il appelle une fonction C appelée enter ayant mis en place une pile simple, étant donné une stacks variable et une taille de pile que vous voulez pour chaque noyau STACK_SIZE (vous ne pouvez pas utiliser sizeof).

asm (
    "\n.global  _start"
    "\n.type    _start, %function"
    "\n.section .text"
    "\n_start:"
    "\n\tmrs     x0, mpidr_el1"
    "\n\ttst     x0, #0x40000000"
    "\n\tand     x1, x0, #0xff"
    "\n\tcsel    x1, x1, xzr, eq" // core
    "\n\tadr     x0, stacks"
    "\n\tmov     x3, #"STACK_SIZE                                                                                       
    "\n\tmul     x2, x1, x3"
    "\n\tadd     x0, x0, x2"
    "\n\tadd     sp, x0, x3"
    "\n\tb     enter"
    "\n\t.previous"
    "\n.align 10" ); // Alignment to avoid GPU overwriting code


0 commentaires