IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Arduino Discussion :

Comment déboguer un programme qui part n'importe où ?


Sujet :

Arduino

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé Avatar de pcdwarf
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2010
    Messages
    269
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 269
    Par défaut Comment déboguer un programme qui part n'importe où ?
    Bonjour,

    J'aimerai savoir si il existe une façon de détecter le la collision stack / heap sur les AVR (plus précisément le atmega328 hyper utilisé pour les arduino.)
    Je viens de passer 4 jours a trouver un bug (pointeur d'objet altéré par un débordement dans une interruption et l'appel de méthode qui fait n'importe quoi )
    Le debugage a été épique : le crash n'avait pas du tout lieu à l'endroit de l'erreur.

    A ma connaissance il n'est pas possible de lever une interruption sur invalidopcode ou sur un pointeur de stack hors limites.
    mais peut-être qu'il y a des méthodes software qui permettent d'arriver au même résultat.

    Quelqu'un a une piste ?

  2. #2
    Modérateur

    Avatar de Vincent PETIT
    Homme Profil pro
    Consultant en Systèmes Embarqués
    Inscrit en
    Avril 2002
    Messages
    3 252
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Pas de Calais (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Consultant en Systèmes Embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Avril 2002
    Messages : 3 252
    Par défaut
    Salut,
    En effet, il n'y a pas de vecteurs d'interruption pour un stackoverflow
    Source Interrupt Definition (pour ton ATMEGA328P)
    RESET External Pin, Power-on Reset, Brown-out Reset and Watchdog System Reset
    INT0 External Interrupt Request 0
    INT1 External Interrupt Request 1
    PCINT0 Pin Change Interrupt Request 0
    PCINT1 Pin Change Interrupt Request 1
    PCINT2 Pin Change Interrupt Request 2
    WDT Watchdog Time-out Interrupt
    TIMER2 COMPA Timer/Counter2 Compare Match A
    TIMER2 COMPB Timer/Counter2 Compare Match B
    TIMER2 OVF Timer/Counter2 Overflow
    TIMER1 CAPT Timer/Counter1 Capture Event
    TIMER1 COMPA Timer/Counter1 Compare Match A
    TIMER1 COMPB Timer/Coutner1 Compare Match B
    TIMER1 OVF Timer/Counter1 Overflow
    TIMER0 COMPA Timer/Counter0 Compare Match A
    TIMER0 COMPB Timer/Counter0 Compare Match B
    TIMER0 OVF Timer/Counter0 Overflow
    SPI, STC SPI Serial Transfer Complete
    USART, RX USART Rx Complete
    USART, UDRE USART, Data Register Empty
    USART, TX USART, Tx Complete
    ADC, ADC Conversion Complete
    EE, READY EEPROM Ready
    ANALOG, COMP Analog Comparator
    TWI, 2-wire Serial Interface
    SPM READY, Store Program Memory Ready
    Car ce n'est pas possible. Lorsque tu programmes en assembleur tu dois, toi même, initialiser la stack à l'adresse que tu souhaites, le reste de la RAM étant le heap. Avec un langage comme le C, c'est ton compilateur qui fait ce boulot mais dans tous les cas la vérification de la pile est une des choses que l'on doit vérifier lors de la phase de débug.

    Et encore tu es chanceux, étant donnée que ton micro Atmel a une architecture Harvard (mémoire RAM sur un bus d'adresse/donnée différents de la mémoire programme) si la stack déborde trop au point de toucher le compteur programme (qui est empiler aussi lors de l'appel d'une fonction), elle va altérer la RAM et sortie de la fonction où tu étais tu vas te retrouver n'importe où dans ton programme. Si tu avais été sur un MSP430 de Texas qui a une architecture Von-Neumann (tout est sur les mêmes bus donc RAM puis Registres internes puis Mémoire programme dans la même continuité) si la stack déborde trop au point de toucher le compteur programme, elle va altérer la RAM ou des registres (Timer, ADC, GPIO) et sortie de la fonction où tu étais tu vas te retrouver n'importe où dans ton programme... alors là pour trouver ce qui se passe quand même le hard se met a faire n'importe quoi Par contre cette architecture à d'autres avantages, bref.

    D'un point de vu soft tu n'as aucun moyen de voir ce qui se passe de manière sur car dit toi que à partir du moment où tu ajoutes une seule instruction dans le code, tu vas changer la nature du bug. Si tu as un débordement de pile qui, en plus d'altérer la RAM, vient modifier le compteur programme, si tu ajoutes une seule instruction quelque part alors au moment de la sortie de la fonction le compteur programme, ayant été modifié, alors tu vas sortir n'importe où dans le programme mais le fait d'avoir ajouté cette malheureuse instruction va peut être tout faire changer dans ce qui se passe et dans ce que tu vois. Pire encore tu pourrais même masquer le bug !

    Atmel a une sonde de débug ATMEL ICE, à 80€ de mémoire, qui te permettra de faire des points d'arrêts, de dérouler le programme en pas à pas et de voir ce qui se passe dans tes registres en direct sous Atmel Studio (je n'ai pas de Atmel ICE mais je l'ai souvent fait avec la sonde de débug/prog Pickit de Microchip et aussi avec les solutions Texas Instruments comme le MSP-EXP430G2 où la sonde de débug est carrément sur la démo board). Un minimum de connaissance en assembleur peut aider aussi pour comprendre.

    ps : Tu parles de méthode et d'objet, tu programmes ton micro en quel langage ?

  3. #3
    Membre éclairé Avatar de pcdwarf
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2010
    Messages
    269
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 269
    Par défaut
    Bonjour,

    merci pour ta réponse.
    Ce que tu décris est exactement ce qui s'est passé mais pas pour la raison que j'ai cru.
    ce n'était pas une collision heap/stack. C'était une (gigantesque) erreur d'adressage sur un tableau dans un objet statique (segment .bss) et ça conduisait à altérer la stack.

    Par la suite, les retour de fonctions partaient n'importe où.
    Le micro a même exécuté une fonction crée spécialement pour le debug et qui n'était appelée nulle-part.

    Et accessoirement, ça a shooté l'eeprom en écrivant en boucle dedans.
    Le fail complet quoi...

    du moment où tu ajoutes une seule instruction dans le code, tu vas changer la nature du bug.
    C'est pour ça que j'ai mis tant de temps à trouver.... Et ce qui est énorme, c'est que ce bug s'est révélé suite a une modification mineure dans un module qui n'avait rien à voir avec l'emplacement de l'erreur. La version précédente (tout aussi monstrueusement buggée) tournait miraculeusement depuis plus de six mois.
    Maintenant le vrai mystère c'est comment ça a pu fonctionner avant...

    ps : j'ai programmé en C/C++ avec gcc-avr. Par contre, j'ai pas de programmateur "officiel" et c'est pourquoi je n'emploie pas atmel studio (accessoirement, je travaille pas sous windows).
    existe-t-il d'autre soft pour le debuggage ?
    J'ai un vieux pickit2 quelque part. tu as l'air de dire que c'est utilisable ? si oui, comment ?
    Concernant l'assembleur, il y a 12 ans que j'en ai pas fait, et c'était sur 68hc11. J'ai une assez bonne compréhension du fonctionnement de l'ALU mais j'ai pas trop envie d'apprendre l'assembleur AVR. Rien que de pouvoir inspecter la ram à chaque breakpoint, ça doit pouvoir m'aider considérablement.

  4. #4
    Modérateur

    Avatar de Vincent PETIT
    Homme Profil pro
    Consultant en Systèmes Embarqués
    Inscrit en
    Avril 2002
    Messages
    3 252
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Pas de Calais (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Consultant en Systèmes Embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Avril 2002
    Messages : 3 252
    Par défaut
    Citation Envoyé par pcdwarf Voir le message
    Maintenant le vrai mystère c'est comment ça a pu fonctionner avant...
    Oula, j'ai déjà eu des drôles de tour aussi, j'avais juste ajouté 1 malheureuse variable dans une fonction mais le problème c'est que j'avais trop de fonctions qui appelaient d'autres fonctions qui elles mêmes en appellaient d'autres... et ma stack était pleine lorsque toutes les fonctions étaient appelées et cette malheureuse variable a tout foutu en l'air car ma stack à fait : stack pleine + 1 variable a stocker et hop...... dans les choux. Je te crois sur parole quand tu dis "on cherche le bug un moment" mais là où j'ai été chanceux c'est sur la cause du problème, c'est à dire une seule variable en plus. Maintenant j'imagine même pas si j'avais changer une grosse partie du programme (du genre un coup je supprime du code, un coup j'en ajoute, ensuite je modifie des variables, j'en ajoute, je rechange et pas de bol à la fin de tout ça, ça fait 1 variable de plus à stocker par rapport à avant... et tout plante)

    Citation Envoyé par pcdwarf Voir le message
    ps : j'ai programmé en C/C++ avec gcc-avr. Par contre, j'ai pas de programmateur "officiel" et c'est pourquoi je n'emploie pas atmel studio (accessoirement, je travaille pas sous windows).
    existe-t-il d'autre soft pour le debuggage ?
    Tout comme moi, je suis sous Debian 7 + Eclipse + GCC-AVR ou GCC-MSP430 + plugin Arduino ou plugin MSP430, ça dépend de ce que je fais. Malheureusement non, il n'y a rien à ma connaissance pour les ATMEGA328P hormis la sonde Atmel ICE. C'est pour cette raison que je me suis tourner vers Texas pour mes projets car eux, pas besoin de sonde si les bons composants sont implantés.

    Citation Envoyé par pcdwarf Voir le message
    J'ai un vieux pickit2 quelque part. tu as l'air de dire que c'est utilisable ? si oui, comment ?
    Excuse moi, je me suis affreusement mal exprimé ! Je voulais dire que j'avais souvent dégugger des micro, notamment PIC avec Pickit.

    Citation Envoyé par pcdwarf Voir le message
    Concernant l'assembleur, il y a 12 ans que j'en ai pas fait, et c'était sur 68hc11. J'ai une assez bonne compréhension du fonctionnement de l'ALU mais j'ai pas trop envie d'apprendre l'assembleur AVR. Rien que de pouvoir inspecter la ram à chaque breakpoint, ça doit pouvoir m'aider considérablement.
    Ok (on a appris l'assembleur sur le même dinosaure apparemment) mais ça ne sera peut être pas la peine car bien souvent ce problème est facile a résoudre une fois qu'on l'a détecté ! Visiblement, lorsque je lis ton message, je pense que tu n'as pas besoin de sonde car il te suffit de réintégrer le plus possible de fonction dans le programme principal. Vérifie les profondeurs d'appels : combien de fonction sont appelées de manière imbriquées et bien sur pas de fonctions récursives. Certes ça sera au dépend de la lisibilité mais ça va soulager la stack.

    Ton programme est vraiment gros ? Je peux le voir ?

  5. #5
    Membre éclairé Avatar de pcdwarf
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2010
    Messages
    269
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 269
    Par défaut
    Bonjour.

    Mon client ne voudrait probablement pas que je montre le programme à qui que ce soit mais je peux t'en mettre un extrait

    Le programme n'est pas si gros que ça. il a été initialement fait pour le atmega88. mais il fallait vraiment tasser, c'était pas confort. et vu la différence de prix des µC.....

    En gros le µC est un esclave modbus. (une version limitée de modbus rtu).
    Les "adresses" 0 à X correspondent à des registres de "configuration" qui ne changent que très rarement. Pour économiser de la ram (on était vraiment à l'étroit sur le 88) Ils sont écrit en eeprom quand on en donne l'ordre et sont lu en eeprom à chaque fois que le programme en a besoin.

    Pour les adresses plus grandes que X, ce sont de valeur variables qui sont stockées en ram dans un tableau statique. les adresses plus grande que ce tableau sont ignorées


    voila la version buggée


    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    #define ModbusRegistersEEPROMBaseAddr 0
    #define ModbusRegistersEEPROMLastAddr 0x4F
    #define ModbusRegistersRAMBaseAddr ( ModbusRegistersEEPROMLastAddr +1 )
    #define ModbusRegistersRAMLastAddr 0x7F
     
    uint16_t ModbusRegisters::Get16Reg(uint8_t addr)
    {   if (addr > ModbusRegistersRAMLastAddr ) return 0xDEAD;   // On a demandé une adresse invalide
        if ( addr <= ModbusRegistersEEPROMLastAddr ) return EEPROM.get16(addr);    
        return Registers[addr-ModbusRegistersRAMBaseAddr];
    }
     
     
    void ModbusRegisters::Set16Reg(uint8_t addr, uint16_t value)
    {   if (addr > ModbusRegistersRAMLastAddr ) return;  // On a demandé une adresse invalide
        if ( addr <= ModbusRegistersEEPROMLastAddr ) EEPROM.set16(addr, value);      ///<<aie!
        Registers[addr-ModbusRegistersRAMBaseAddr] = value;                          ///<<aie!
    }

    la fonction Set a été dérivée un peu trop rapidement de la fonction Get
    (manque un else ou un return)

    du coup, quand addr < ModbusRegistersRAMBaseAddr ,
    Registers[addr-ModbusRegistersRAMBaseAddr] pointe n'importe où.
    Probablement que la plupart du temps ça arrivait en dehors de la zone utilisée (peut être même en dehors des adresses où il y a physiquement de la ram) ***
    (donc écriture en eeprom ok et pas de bug fonctionnel)
    mais dans ma nouvelle version, ça altère la stack. et catastrophe...



    Le crash en revanche se produisait très très loin de l'endroit du bug...
    la difficulté a été de comprendre que l'altération était (très) loin du lieu du crash dans un bout de code réputé bon depuis des mois.




    -----

    *** : Je me demande si le décodeur d'adresse fait le modulo quand on sort de la plage où il y a physiquement quelque chose ou si ça ce contente d'écrire dans le vide?

  6. #6
    Modérateur

    Avatar de Vincent PETIT
    Homme Profil pro
    Consultant en Systèmes Embarqués
    Inscrit en
    Avril 2002
    Messages
    3 252
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Pas de Calais (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Consultant en Systèmes Embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Avril 2002
    Messages : 3 252
    Par défaut
    D'accord, j'y vois plus clair.
    Ta table MODBUS est faite comme ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    EEPROM 	0x00 à 0x4F
    RAM 	0x50 à 0x7F
    Suivant l'endroit à l'on veut écrire dans la table, au travers de la variable addr on sera soit en RAM soit en EEPROM

    Effectivement comme tu le fais remarquer l'algorithme de Set16Reg est buggé est il provoque l'équivalent d'un SegmentFault, remonté d'un OS sur un PC.
    Ici on a un simple débordement de tableau et comme tu l'as bien vu qui va aller foutre le bordel dans la RAM (qui contient entre autre la stack logicielle)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    Si addr > 7F alors
    	return car hors mémoire
     
    Si addr <= 0x4F (sous entendu compris entre 0 et 4F) alors
    	EEPROM <- valeur
     
    RAM [addr - 0x50] <- valeur
    Lorsque l'adresse à la quelle on veut écrire est inférieure à 0x4F alors on l'écrit dans l'EEPROM et aussi en RAM (sauf qu'en RAM on cause un débordement du tableau) imagine que l'adresse soit inférieur à 0x50 ? Alors l'index du tableau est négatif ! Donc a chaque fois qu'on écrit en EEPROM on fait un débordement de tableau.

    La bonne solution est celle que tu dis : Ajouter un return
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    Si addr > 7F alors
    	return car hors mémoire
     
    Si addr <= 0x4F (sous entendu compris entre 0 et 4F) alors
    	EEPROM <- valeur
            return
     
    RAM [addr - 0x50] <- valeur

    Donc ça, ça ne devrait plus buger.
    Code C : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    void ModbusRegisters::Set16Reg(uint8_t addr, uint16_t value)
    {   if (addr > ModbusRegistersRAMLastAddr ) return;  // On a demandé une adresse invalide
        if ( addr <= ModbusRegistersEEPROMLastAddr ) return EEPROM.set16(addr, value);      ///<<aie!
        Registers[addr-ModbusRegistersRAMBaseAddr] = value;                          ///<<aie!
    }

    Mais depuis le début tu as la solution ? Pourquoi tu poses cette question du coup (comment débuger un programme qui part n'importe où) ?
    A+

Discussions similaires

  1. Réponses: 3
    Dernier message: 06/01/2016, 07h03
  2. comment arrêter un programme qui tourne en boucle
    Par isa3000 dans le forum Langage
    Réponses: 12
    Dernier message: 07/09/2009, 16h54
  3. Réponses: 23
    Dernier message: 30/06/2007, 18h14
  4. comment faire un programme qui calcul la somme ?
    Par jahjouna dans le forum C++
    Réponses: 18
    Dernier message: 13/12/2006, 00h33

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo