Programme plus évolué

Fibonacci

❎ Écrivez une fonction récursive int fibo(int n), qui calcule le n-ième nombre de la suite de Fibonacci.

❎ Modifiez main pour qu'il ne fasse que renvoyer fibo(8).

❎ Compilez, et vérifiez avec objdump que le programme est bien logé aux bonnes adresses.

❎ Testez-le en vrai. Que se passe-t-il, et pourquoi ?

Indice crucial : exécutez le programme instruction assembleur par instruction assembleur et vérifiez à chaque étape que tout s'est bien passé :

  • pour toute opération arithmétique / logique, vérifiez le résultat en examinant les registres ;
  • pour tout accès à la mémoire, regardez le contenu de la mémoire avant l'instruction puis après l'instruction et vérifiez que c'est cohérent.

Correction des choses

Vous venez de constater qu'il manque quelque chose de crucial avant main pour que les choses s'exécutent correctement.

❎ Créez donc le fichier qui va bien, qui se chargera de mettre en place un environnement d'exécution correct pour le code C.

❎ Compilez, et vérifiez avec objdump que le programme est bien logé aux bonnes adresses.

❎ Testez votre programme, qui doit à présent s'exécuter correctement.

Attention :

  • L'assembleur a besoin de connaître le processeur cible : -mcpu=cortex-m4.
  • Contrairement au C, en assembleur les symboles sont privés par défaut. Pour les exporter (de façon à ce qu'ils soient visibles depuis le C ou le linker script), il faut les déclarer .global.
  • On utilisera la syntaxe unifiée, donc la directive .syntax unified.
  • Enfin il faut dire à l'assembleur qu'on compile en mode thumb : .thumb.

Exemple :

    .syntax unified
    .global _start
    .thumb

_start:
    blablabla

Initialisation du BSS

❎ Dans le fichier que vous venez de créer, appelez avant main une procédure void init_bss() (écrite en C dans un fichier appelé init.c), qui se chargera d'initialiser le BSS à zéro, en s'aidant de symboles exportés depuis le script de link.

❎ Testez cette procédure en déclarant des variables qui seront stockées dans le BSS et en vérifiant au débugger qu'une fois arrivé à main, elles valent bien 0.

Mais pourquoi le compilateur appelle memset ?

En fonction de la version du compilateur et du niveau d'optimisation que vous utilisez, la boucle d'initialisation du BSS que vous avez écrite peut être remplacée automatiquement par le compilateur par un appel à la fonction memset. Or, si vous avez choisi de ne pas lier votre exécutable avec la bibliothèque standard (-nostdlib) conformément à la philosophie de l'UE, l'éditeur de liens ne trouvera pas le code de cette fonction et générera donc une erreur.

❎ À partir de maintenant, ajouter le drapeau -ffreestanding lors de vos appels au compilateur pour lui indiquer que l'on est dans un environnement où il n'y a pas de bibliothèque standard.

Néanmoins, même dans ce cas, il est nécessaire (c'est précisé dans la documentation de gcc) de fournir une implémentation pour quatre fonctions que gcc peut choisir d'utiliser à certains endroits : memcpy, memmove, memset et memcmp.

❎ Dans un fichier nommé memfuncs.c, fournissez l'implémentation de ces quatre fonctions (n'hésitez pas à consulter les pages de manuel de ces fonctions pour obtenir leur signature et ce qu'elles doivent faire).

Après le main

❎ Dans un système embarqué, main ne devrait jamais terminer. Au cas où ça arriverait, pour éviter que le processeur aille exécuter ce qui se trouve dans la zone de code après l'appel à main, insérez après l'appel à main une boucle infinie sur un symbole appelé _exit.