Les bouchons sont inévitables lors du développement de tout logiciel. Dans une intégration, leurs cinq cents généreux peuvent également provoquer des problèmes matériels, mais il s'agit d'une chanson distincte. Mais des embuscades purement programmées, quand vous êtes coincé dans un endroit apparemment vide ... Pour moi, il y en a trois types.
La manière la plus simple est lorsque le manuel, standard ou, disons, la procédure de configuration de la bibliothèque pour le fer n'est pas entièrement compris. C'est clair ici: tous les mouvements n'ont pas été épuisés, patience et travail, encore cinq ou deux expériences, et cela prendra vie. Oscilloscope et tyk scientifique pour aider.
Choisir un diviseur de fréquence pour configurer le bus CANPire encore, lorsque le problème est une faute de frappe ou une erreur de logique que vous ne pouvez pas voir à bout portant jusqu'à ce que vous traversiez cet endroit vingt fois avec vos yeux et que vous déboguiez pas à pas. Puis il se lève, un coup sonore au front, un cri: «Eh bien, espèce de babai!», Édition. Ça marche.
Et une troisième vue sombre: un pépin retranché dans une bibliothèque étrangère et rampant à la jonction avec du fer. Les passions shakespeariennes donnent naissance à la lumière fixe d'un moniteur. «Pourquoi, il ne peut pas, le système ne peut pas se comporter de cette façon, car il ne peut jamais! Eh bien, vraiment! Ah?! " Non. Recevez, signez.
En conséquence, la réalité est plus large, plus large et plus large que prévu. Quelques exemples:
Histoire n ° 1. Lecteur flash MicroSD et travail DMA
Anamnèse
Vous devez sauvegarder les données dans un fichier sur la carte SD. Bien sûr, je n'ai ni le temps ni l'envie d'écrire le système de fichiers et le pilote SDIO moi-même, alors je prends la bibliothèque terminée. Je l'ai configuré pour le fer et tout fonctionne bien. Au début. Et puis il s'avère que les données ont été enregistrées de manière extravagante: les volumes sont précis, mais dans les fichiers eux-mêmes, des paires d'octets distinctes sont dupliquées, puis disparaissent, sans aucune régularité. Pas bon!
Les expériences commencent. J'écris des données de test - tout va bien. J'écris le combat - une sorte de diabolique. Je change la taille des tampons de données, la fréquence de leur vidage, les modèles de données sont inutiles. Dans les tampons eux-mêmes, tout est toujours excellent, les données en mémoire sont partout ce dont vous avez besoin. Et, néanmoins, pépins sur un lecteur flash - les voici.
Il a fallu quelques jours pour creuser le chien.
Le diagnostic
Le problème résidait dans l'interaction de la bibliothèque avec l'équipement
DMA .
Les cartes SD ont une particularité: elles ne sont écrites que par blocs de 512 octets. Pour ce faire, la bibliothèque met en mémoire tampon les données dans un tableau de 512 octets, et lors du remplissage, elle en vide via DMA pour flasher. Mais!
Si je transfère à l'enregistrement un fragment supérieur à <512xN + espace vide dans le tampon de bibliothèque> octets, alors la bibliothèque (évidemment, pour ne pas pousser la mémoire d'avant en arrière) le fait: elle reconstitue son tampon, l'écrit sur flash , et les 512xN octets suivants sont jetés directement dans mon DMA depuis ma mémoire tampon! Eh bien, si quelque chose reste inachevé - il se copie à nouveau, jusqu'à la prochaine fois.
Et tout irait bien, mais le contrôleur DMA exige que les données soient placées en mémoire alignées sur une limite de 4 octets. Le tampon de la bibliothèque est toujours si aligné, le langage le garantit. Mais avec quelle adresse, après avoir copié une partie des données, les 512xN restants avec un petit octet commencent par moi - Dieu sait. Et la bibliothèque ne vérifie pas cela du tout: l'adresse, telle qu'elle est, est transmise au contrôleur DMA.
"Ils ont envoyé quelque chose de maladroit ... Un chien avec lui." Le contrôleur réinitialise silencieusement les 2 bits inférieurs de l'adresse transmise. Et commence le transfert.

L'adresse, initialement pas un multiple de 4, est remplacée par un multiple - voila, les trois derniers octets du tampon de bibliothèque sont réécrits dans le fichier à partir du mien, et le même nombre d'octets de mon tampon est perdu sans trace. En conséquence, la quantité totale de données est correcte, les opérations se déroulent sans heurts, mais le disque n'a pas de sens.
Le traitement
J'ai dû ajouter un autre tampon immédiatement avant d'appeler la fonction d'enregistrement matériel. Si l'adresse d'écriture n'est pas un multiple de 4, les données y sont d'abord copiées. Dans le même temps, la vitesse moyenne a augmenté en raison d'un choix raisonnable de taille de tampon. Bien sûr, cela a pris de la mémoire, mais qu'est-ce que 4 kilo-octets pour une bonne cause, quand vous en avez à votre disposition - 192 sans limites!
Histoire n ° 2. Rantime et un tas
Prologue
Après le changement suivant, le programme a commencé à tomber et, d'une manière ou d'une autre, il est tombé très fort, jetant le processeur dans le gestionnaire de
faute matérielle. Et il l'a jeté là juste après le début, avant même que l'exécution n'arrive à main (), c'est-à-dire qu'aucune ligne de mon code n'a eu le temps de s'exécuter.
La première impression est «le castor est mort, la puce est à remplacer». Et puis le programmeur a donné le chêne. Mais non, l'ancienne version du firmware fonctionne de manière stable, mais la nouvelle tombe régulièrement dans des profondeurs d'assemblage obscures entre le lancement et mon code. Je n'avais aucune hypothèse sur le type d'hérésie.
Chapitre 1
A aidé Internet à regarder comment obtenir au moins quelques informations supplémentaires. La procédure d'analyse syntaxique des conséquences d'un défaut matériel a été googlé: état des registres, pile de vidage. Dopilil. Je l'ai utilisé.
Il s'est avéré qu'il se bloque en raison d'une erreur de fonctionnement sur le bus. J'ai décidé que c'était à nouveau un accès déséquilibré - un problème du même type que dans la première histoire, mais dans une perspective différente. Mais le plus opposé est l'endroit où l'erreur s'est produite. Et il est apparu à l'intérieur de la bibliothèque d'exécution, c'est-à-dire dans le code, qui, en théorie, a été léché comme les ecchymoses du chat par une journée ensoleillée.
La poursuite de l'analyse a montré que le glitch est la conséquence d'une tentative d'initialisation des variables statiques locales.
Digression lyriqueSoit dit en passant, compte tenu du code désassemblé, j'ai simultanément trouvé la réponse à une question que je me posais parfois, mais j'étais trop paresseux pour google tout de suite: comment la situation est-elle résolue lorsque 2 threads ou plus peuvent essayer d'initialiser une telle variable en même temps. Il s'est avéré que dans ce cas, le compilateur organise l'initialisation avec des sémaphores, garantissant qu'un seul thread à la fois passera par toute la procédure et que le reste attendra la fin du premier.
Ce comportement a été normalisé depuis C ++ 11. Saviez vous Non.
Chapitre 2
Une fois que le runtime est engagé dans la construction de variables, il lui appartient également d'appeler des destructeurs à la fin du programme (même si le programme ne termine jamais réellement le travail, ce qui est la norme absolue pour les microcontrôleurs). Pour ce faire, il a besoin d'un endroit pour stocker des informations sur toutes les variables qu'il a réussi à initialiser.
C'est juste à l'endroit où ces informations sont stockées dans une sorte de liste interne, le temps d'exécution est également tombé. Parce que la fonction malloc (), à travers laquelle la mémoire a été allouée pour les éléments de cette liste et qui, selon la norme, produit des blocs garantis alignés
au moins à la limite de 8 octets , après un nième nombre d'appels réussis, elle produit une pièce qui n'est pas alignée à cette limite.

Les changements dans le nouveau code du firmware ont cassé malloc?! Mais comment est-ce encore possible? Je n'ai pas redéfini exactement malloc; je n'en ai moi-même pas besoin ailleurs!
Utile dans les options du compilateur, pour rechercher des mots-clés, aide, mais c'était clairement dit partout:
malloc () garantit la sortie de la mémoire alignée le long de la frontière fondamentale. Ou pointeur nul en cas de mémoire insuffisante .
Chapitre 3
Pendant longtemps, je suis resté insensé dans le code, fixé des points d'arrêt, souffert et je n'ai rien compris, jusqu'à ce que cela ne se soit pas produit et j'ai regardé attentivement les adresses retournées par malloc. Avant cela, toute l'analyse consistait à voir si le dernier chiffre de l'adresse était 0x4. Et maintenant, il a commencé à comparer entièrement les adresses émises par des appels successifs à malloc.
Et oh, un miracle!
Tous les appels réussis ont émis des adresses à partir de l'espace RAM (0x20000000 et plus pour cette pierre), augmentant séquentiellement d'un appel à l'autre. Et le premier échoué a renvoyé 0x00000036. Autrement dit, l'adresse n'est pas seulement qu'elle n'était pas alignée, mais elle n'était pas du tout dans l'espace d'adressage de la RAM! Le processeur a essayé d'y écrire quelque chose et est tombé naturellement.
Et, étonnamment, même si malloc () agissait selon la norme et renvoyait 0 s'il n'y avait pas assez d'espace, cela n'aurait rien changé dans le sens d'un plantage du programme (à moins que la cause du bogue n'ait été clarifiée plus tôt). La valeur retournée par malloc n'est toujours pas vérifiée, mais entre immédiatement en action. C'est en runtime.
Épilogue
Augmentation de la taille du segment de mémoire dans le fichier de configuration et tout a été corrigé.
Mais avant ce moment, je ne pensais même pas à son volume. Que l'enfer se soit rendu à moi, pensai-je. Quoi qu'il en soit, j'ai toutes les variables et les objets soit statiques, soit sur la pile. Donc, juste par inertie, j'ai laissé 0x300 octets en dessous, car un volume sous le tas est alloué dans tous les projets de modèle. Mais non, le runtime C ++ a toujours besoin de mémoire allouée dynamiquement, et en quantité assez notable, selon les normes des contrôleurs.
Vivez et apprenez.