Forum Programmation.c Code assembleur incorrect généré à partir du code C (ARM Cortex-m3) ?

Posté par  . Licence CC By‑SA.
Étiquettes :
1
2
déc.
2021

Hello,

J'ai un problème avec un code assembleur généré par GCC à partir d'un code C.

J'essaie d'écrire un firmware minimal pour un microcontrôleur, le LPC1769.
Il utilise le processeur ARM Cortex-M3. Mon firmware est simple :
* Exécute une fonction nommée "reset()" lors de la réinitialisation du processeur
* Dans cette fonction, je déclare 3 variables locales : 3 pointeurs vers 3
différents registres, initialisés avec leur adresse
* Ensuite, j'écris une valeur dans chaque registre

Le problème:

Le CPU est réinitialisé après la première valeur que j'écris dans un registre

Le code de la fonction "reset()":

void reset() {
  // Define some registers
  // Register to define mode of the pins P0.16 to P0.26
  unsigned int *PINMODE1 = (unsigned int *)0x4002C044;
  // Register to define GPIO direction of pins P0.0 to P0.31
  unsigned int *FIO0DIR = (unsigned int *)0x2009C000;
  // Register to define GPIO value of pins P0.0 to P0.31
  unsigned int *FIO0SET = (unsigned int *)0x2009C018;

  // Config the GPIO to drive a LED
  // Enable P0.22 pin to put the line to ground
  *PINMODE1 = 0x00003000;
  // Set P0.22 as GPIO output
  *FIO0DIR = 0x400000;

  // To the eternity
  while (1) {
    // Wait
    for (int i = 0; i < 500000; ++i);

    // Toggle the LED via P0.22
    *FIO0SET ^= (0b1<<22);
  }

}

Le problème se situe à la ligne *PINMODE1 = 0x00003000;.
Le code assembleur généré par GCC et vue depuis GDB :

=> 0x0000001a <+18>:    ldr r3, [r7, #8]
   0x0000001c <+20>:    strb    r6, [r0, #28]
   0x0000001e <+22>:            ; <UNDEFINED> instruction: 0xf590601a
   0x00000022 <+26>:    ldr r3, [r7, #4]

Le premier code assembleur, ldr, charge l'adresse 0x4002C044 dans le
enregistrer r3. Mais le deuxième code asm, strb, stocke-t-il la valeur
du registre r6 à l'adresse faite à partir de la valeur du registre
r0 plus un décalage de 28 ? Pourquoi ne pas simplement copier la valeur 0x00003000
à l'adresse stockée dans le registre r3' ? Pourquoi cette instruction indéfinie
0xf590601a ? Ce troisième code asm, le "undefied" 0xf590601a, est l'instruction qui réinitialise le CPU.

Je ne suis pas un expert en assembleur et je ne sais pas si c'est moi qui ne le comprend pas
ou si le code assembleur généré par GCC est erroné. Toute
aide est la bienvenue. ;)

Merci. :)

Voici la version de arm-none-eabi-gcc que j'utilise :
11.1.0 (Fedora 11.1.0-2.fc35)

Version de GDB :
11.1 (Fedora 11.1-2.fc35)

Version d'OpenOCD :
0.11.0

Voici le code C complet :

/* reset

Function run right after the reset of the CPU
 */
void reset() {
  // Define some registers
  // Register to define mode of the pins P0.16 to P0.26
  unsigned int *PINMODE1 = (unsigned int *)0x4002C044;
  // Register to define GPIO direction of pins P0.0 to P0.31
  unsigned int *FIO0DIR = (unsigned int *)0x2009C000;
  // Register to define GPIO value of pins P0.0 to P0.31
  unsigned int *FIO0SET = (unsigned int *)0x2009C018;

  // Config the GPIO to drive a LED
  // Enable P0.22 pin to put the line to ground
  /* *PINMODE1 |= (0x1<<12); */
  /* *PINMODE1 |= (0x1<<13); */
  *PINMODE1 = 0x00003000;
  // Set P0.22 as GPIO output
  /* *FIO0DIR |= (0x1<<22); */
  *FIO0DIR = 0x400000;

  // To the eternity
  while (1) {
    // Wait
    for (int i = 0; i < 500000; ++i);

    // Toggle the LED via P0.22
    *FIO0SET ^= (0b1<<22);
  }

}

int STACK[256];

const void *vectors[] __attribute__ ((section (".vectors"))) = {
  STACK + sizeof(STACK) / sizeof(*STACK),
  reset
};

Voici mon link script:

MEMORY {
    flash (RX) : ORIGIN = 0x00000000, LENGTH = 512K
    sram (RW!X) : ORIGIN = 0x10000000, LENGTH = 32K
}
SECTIONS {
    .vectors    : { *(.vectors) } >flash
    .text       : { *(.text) } >flash
    .rodata     : { *(.rodata) } >flash
    .bss        : { *(.bss) } >sram
}

Voici mon Makefile:

CFLAGS = -g -O0 -Wall
CFLAGS += -mthumb -mcpu=cortex-m3 

CC = arm-none-eabi-gcc
LD = arm-none-eabi-ld
OBJCPY = arm-none-eabi-objcopy


all: blink.bin


blink.bin: blink.elf
    @echo "Make the binary"
    $(OBJCPY) -O binary blink.elf blink.bin

blink.elf: blink.o
    @echo "Linkage"
    $(LD) -T blink.ld -o blink.elf blink.o

blink.o: blink.c
    @echo "Compile the code"
    $(CC)  -c $(CFLAGS) -o blink.o blink.c

clean:
    @echo "Clean the working dir"
    rm blink.o blink.elf blink.bin

debug: blink.elf
    gdb -x gdbconfig blink.elf

debug-mi: blink.elf
    gdb -i=mi -x gdbconfig blink.elf
  • # fpic

    Posté par  . Évalué à 3. Dernière modification le 02 décembre 2021 à 16:52.

    J'ai déjà eu des problèmes de compilation très bizarres où j'avais dû moi aussi m'engouffrer dans l'assembleur, et m'y retrouver confus face à ce que j'y voyais. La raison était l'utilisation de l'option de compilation "-fpic", pas implémentée sur Cortex M4.
    Je vois que tu ne l'utilises pas. Je te conseille malgré tout de faire des tests avec une commande de compilation la plus épurée possible, histoire de voir ce que ça donne. Tu es sûr d'avoir besoin de "-mthumb" ?

    • [^] # Re: fpic

      Posté par  . Évalué à 1.

      Merci pour ta réponse. :)

      Avec GCC, la compilation pour l'architecture Cortex-M3 ne supporte que le mode Thumb.

      J'ai regardé à quoi ressemblent les Makefiles utilisés par l'IDE officiel: Même en reprenant les options en lien avec la compilation le CPU reset toujours au même endroit.

  • # Pas certain que ça marche comme approche

    Posté par  (Mastodon) . Évalué à 4. Dernière modification le 03 décembre 2021 à 08:51.

    Je ne suis pas sûr que ton approche soit la bonne. Les microcontrôleurs de ce type sont assez évolués, partir d'une feuille blanche est assez compliqué (il y a souvent des choses à initialiser).

    Mais tu as parfaitement le droit (et l'intérêt) de chercher à tout comprendre et donc d'écrire ton truc minimaliste.

    À ta place je partirais d'un truc qui marche et ensuite je simplifierais jusqu'à l'extrême. Au passage tu valideras tout un tas de truc à commencer par ta chaîne de compilation.

    Sinon en C il me semble que les mots clé volatile et register servent à ça, éviter des optimisations du compilateur.

    En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

    • [^] # Re: Pas certain que ça marche comme approche

      Posté par  . Évalué à 3.

      À ta place je partirais d'un truc qui marche et ensuite je simplifierais jusqu'à l'extrême. Au passage tu valideras tout un tas de truc à commencer par ta chaîne de compilation.

      Je plussoie fortement. À une époque j'ai voulu faire un hôte USB sur microcontrôleur, sans utiliser la biblio du constructeur, ni la biblio standard C. Je me suis complètement cassé les dents à vouloir un truc fonctionnel et minimaliste en même temps; d'un coup. J'aurais dû dès le début partir d'un projet fonctionnel, et l'amincir progressivement petit à petit.

    • [^] # Re: Pas certain que ça marche comme approche

      Posté par  . Évalué à 1.

      Merci pour ta réponse. :)

      Mon objectif est justement de partir d'une feuille blanche.
      J'ai comparé avec le travail d'autre personnes mais je n'ai pas trouvé de différence majeur sur la partie compilation ou initialisation.

      Le LPC1769 a comme avantage d'être très simple:
      * Au reset, il récupère l'adresse du sommet de la stack à l'adresse 0x0000 et la copie dans le registre correspondant
      * Il récupère ensuite l'adresse de la première instruction à exécuter à l'adresse 0x0004 et la copie dans le registre correspondant
      * Il commence l'exécution
      * Au reset, une mémoire, la Boot ROM, est mappée à l'adresse 0x0000:
      * Le code qui s'y trouve s'exécute puis "re-mappe" la flash à l'adresse 0x0000
      * Le code présent sur la flash prend le relais (chargement de l'adresse du sommet de la stack et de la première instruction)
      * Les GPIO sont par défaut alimenté, connecté à une horloge active et connecté à une pin physique du uC

      D'ailleurs, si l'écriture dans les registres depuis du code reset le CPU, avec OpenOCD j'arrive à lire et écrire directement dans les registres sans problème.

      Ce qui me fait dire que le problème vient du code assembleur généré par GCC. Mais je ne trouve pas pourquoi.

  • # MCUXpresso

    Posté par  . Évalué à 2.

    Développant sur ce microcontrôleur je peux t'assurer que faire de l'assembleur, à part s'amuser et progresser en assembleur, est contre productif.

    Il est facile de développer en C avec MCUXpresso de NXP.
    C'est multiplateforme, opensource et tu peux importer les SDK LpcOpen pour disposer de toute la quincaillerie et les drivers bas niveau.
    C'est un super outil basé sur Eclipse et GCC.

    Concernant l’instruction inconnue, c'est peut être lié aux instructions Thumb (des codes instruction 16 bits dans le code 32 bits).
    Regarde les config du compilateur et du debugger pour savoir comment paramétrer la chaine de compil en fonction de ton µCtrl.
    De mémoire, le 1768 utilise Thumb-2.

    Bon courage.

    • [^] # Re: MCUXpresso

      Posté par  . Évalué à 1.

      Merci pour ta réponse. :)

      Mon objectif n'est pas d'être productif, mais d'écrire un firmware from scratch.
      Je me suis basé sur la vidéo de Nikolay Kondrashov qui a écrit un firmware from scratch pour un autre uC avec la même architecture:
      https://media.ccc.de/v/ASG2019-161-microcontroller-firmware-from-scratch

      Avant de commencer, j'ai bien lut le manuel du uC pour savoir comment il démarre, qu'est-ce qui est actif ou pas au démarrage, qu'est-ce qu'il faut initialiser.
      Comme dit dans une de mes réponses plus haut, j'arrive à écrire et lire dans les registres depuis OpenOCD. Donc le problème ne semble pas venir d'une composant non initialisé, mais plutôt du code généré.

      Pour les instructions Thumb: J'ai précisé manuellement à GDB que les instructions sont du Thumb avec la commande set arm force-mode thumb.
      Mais il affiche toujours <UNDEFINED> instruction: 0xf590601a.

  • # problème réglé

    Posté par  . Évalué à 2.

    J'ai finalement réglé le problème.

    Le code assembleur généré par GCC pour une des lignes de mon code C était complétement aux fraises et c'est ce qui produisait un reset du CPU.

    Par contre, je ne sais pas pourquoi GCC générait ce code. Il a cessé dès que j'ai modifié mon code ailleurs que sur la ligne concernée.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.