3 votes

Méthode virtuelle incorrecte (0x0) adresse

Obtenu certains segfaults occasionnels bizarres dans un code appelant des fonctions membres virtuelles. Le segfault se produit environ une fois toutes les 30 000 appels.

J'utilise des méthodes virtuelles pour implémenter un patron de méthode de modèle.

La ligne de code où cela se produit est la première ligne de

GenericDevice::updateValue()
{
     ...
     double tmpValue=getValue();
     Value=tmpValue;
     ...
}

avec

class GenericDevice
{
    public: 
    void updateValue();
    void print(string& result);
    ...
    protected:
    virtual double getValue()const=0;
    ...
    private:
    std::atomic Value;
    ...
}

Une classe GenericDevice est fournie ultérieurement en chargeant une bibliothèque dynamique à l'exécution

class SpecializedDeviced : public
{
    ...
    virtual double getValue()const final;
    ... 
}

J'ai pu obtenir un coredump lorsque le problème est survenu et j'ai regardé le code assemblé:

0x55cd3ef036f4 GenericDevice::updateValue()+92   mov    -0x38(%rbp),%rax   
0x55cd3ef036f8 GenericDevice::updateValue()+96   mov    (%rax),%rax 
0x55cd3ef036fb GenericDevice::updateValue()+99   add    $0x40,%rax  
0x55cd3ef036ff GenericDevice::updateValue()+103  mov    (%rax),%rax 
0x55cd3ef03702 GenericDevice::updateValue()+106  mov   -0x38(%rbp),%rdx
0x55cd3ef03706 GenericDevice::updateValue()+110  mov   %rdx,%rdi         
0x55cd3ef03709 GenericDevice::updateValue()+113  callq  *%rax
0x55cd3ef0370b   movq   %xmm0,%rax          
0x55cd3ef03710   mov    %rax,-0x28(%rbp) 
0x55cd3ef03714   mov    -0x38(%rbp),%rax  
0x55cd3ef03718   lea    0x38(%rax),%rdx     
0x55cd3ef0371c   mov    -0x28(%rbp),%rax    
0x55cd3ef03720   mov    %rax,-0x40(%rbp)    
0x55cd3ef03724   movsd  -0x40(%rbp),%xmm0  

Le segfault est censé s'être produit dans 0x55cd3ef03709 GenericDevice::updateValue()+113.

où
#0  0x000055cd3ef0370a in MyNamespace::GenericDevice::updateValue (this=0x55cd40586698) at ../src/GenericDevice.cpp:22
#1  0x000055cd3ef038d2 in MyNamespace::GenericDevice::print (this=0x55cd40586698,result="REDACTED"...) at ../src/GenericDevice.cpp:50
...

La fonction GenericDevice::updateValue() a été appelée comme prévu.

, std::allocator >&)+301>  callq  0x55cd3ef03698 

Raison pour laquelle rax est défini sur 0x0.

Groupe de registres : général
rax            0x0              0  
rbx            0x5c01b8a2       1543616674  
rcx            0x2              2  
rdx            0x28             40  
rsi            0x2              2  
rdi            0x55cd40586630   94340036191792  
rbp            0x7ffe39086e60   0x7ffe39086e60  
rsp            0x7ffe39086e20   0x7ffe39086e20  
r8             0x7fbb06e7e8a0   140441251473568  
r9             0x3              3  
r10            0x33             51  
r11            0x206            518                       
r12            0x55cd3ef19438   94340012676152  
r13            0x7ffe39089010   140729855283216   
r14            0x0              0   
r15            0x0              0  
rip            0x55cd3ef0370a  0x55cd3ef0370a                     
eflags         0x10206  [ PF IF RF ]               
cs             0x33     51
ss             0x2b     43
ds             0x0      0  
es             0x0      0  
fs             0x0      0   
gs             0x0      0 

En effectuant les calculs à partir de l'extrait d'assemblage, j'ai pu confirmer que le code assemblé et les données qu'il utilise correspondent à l'appel de fonction virtuelle attendu et commencent avec les données correctes.

  1. Le pointeur this de l'objet est utilisé

    (gdb) x /g $rbp-0x38  
    0x7ffe39086e28: 0x000055cd40586698   
    (gdb) p this  
    $1 = (GenericDevice * const) 0x55cd40586698
  2. Le pointeur vers la table des méthodes virtuelles est correct (premier élément de *this)

    (gdb) x 0x000055cd40586698  
    0x55cd40586698: 0x00007fbb070c1aa0
    (gdb) info vtbl this  
    vtable for 'GenericDevice' @ 0x7fbb070c1aa0 (subobject @ 0x55cd40586698):
  3. La table des méthodes virtuelles contient l'adresse de la méthode que nous recherchons.

    (gdb) info vtbl this  
    vtable for 'GenericDevice' @ 0x7fbb070c1aa0 (subobject @ 0x55cd40586698):  
    ...  
    [8]: 0x7fbb06e7bf50 non-virtual thunk to MyNamespace::SpecializedDevice::getValue() const.
  4. Le décalage correct pour la table des méthodes virtuelles est utilisé

    (gdb) x 0x00007fbb070c1aa0+0x40  
    0x7fbb070c1ae0 <_ZTVN12MyNamespace11SpecializedDeviceE+168>: 0x00007fbb06e7bf50

Conclusion jusqu'à présent : en parcourant le code assembleur, l'utilisation de données et d'instructions correctes a été validée.

  • Des données correctes ont été utilisées : la corruption de la mémoire peut être écartée.
  • Les instructions assemblées semblent correctes : les erreurs de codage/de compilation peuvent être exclues.
  • La table des méthodes virtuelles semble correcte : une erreur lors du chargement de la bibliothèque à l'exécution peut être exclue. De plus, la fonction fonctionne habituellement bien des dizaines de milliers de fois.

N'hésitez pas à signaler toute erreur dans mon raisonnement.

Pourtant la valeur dans le registre rax est zéro au lieu de 0x7fbb070c1ae0 attendu

  • Cela pourrait-il indiquer une erreur matérielle dans l'un (rarement utilisé) des cœurs de CPU ? Expliquerait l'occurrence rare et aléatoire mais je m'attendrais à des problèmes avec d'autres programmes et le système d'exploitation également.

Le modèle du processeur est Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz

Merci d'avance !

Mise à jour : J'ai trouvé le marqueur $RIP
0x55cd3ef0370a MyNamespace::GenericDevice::updateValue()+114 shlb 0x48(%rsi)

L'assemblage affiché par gdb semble changer après avoir fait défiler. C'est pourquoi je n'ai pas vu le marqueur lors du premier essai. Après avoir démarré gdb et tapé layout asm, j'obtiens :

>0x55cd3ef0370a   shlb   0x48(%rsi)           
0x55cd3ef0370d   movd   %mm0,%eax            
0x55cd3ef03710   mov    %rax,-0x28(%rbp)     
0x55cd3ef03714   mov    -0x38(%rbp),%rax     
0x55cd3ef03718   lea    0x38(%rax),%rdx   
0x55cd3ef0371c   mov    -0x28(%rbp),%rax
0x55cd3ef03720   mov    %rax,-0x40(%rbp)
0x55cd3ef03724   movsd  -0x40(%rbp),%xmm0  

...

Après avoir fait défiler l'assemblage dans gdb, j'obtiens le code posté dans la question initiale. Le code de la question originale correspond au code du fichier exécutable. Le code posté ci-dessus s'écarte partiellement de l'exécutable.

L'instruction shlb n'a aucun sens pour moi. Je n'ai même pas trouvé cette instruction dans le Manuel du développeur de logiciels pour les architectures Intel® 64 et IA-32. La correspondance la plus proche était shl.

1voto

Employed Russian Points 50479

Comme l'a noté @Jester, vos autres valeurs d'enregistrement ne correspondent pas au code dans lequel vous dites que le crash s'est produit.

J'ai pu obtenir un coredump lorsque le problème s'est produit et j'ai regardé le code en assembleur : ... Le segfault s'est produit dans la dernière ligne de l'extrait d'assembleur.

Comment le savez-vous ? Quel est le résultat de where?

Normalement, il devrait y avoir un marqueur $RIP actuel, comme ceci :

   0x55cd3ef036f4 GenericDevice::updateValue()+92   mov    -0x38(%rbp),%rax   
   0x55cd3ef036f8 GenericDevice::updateValue()+96   mov    (%rax),%rax 
   0x55cd3ef036fb GenericDevice::updateValue()+99   add    $0x40,%rax  
   0x55cd3ef036ff GenericDevice::updateValue()+103  mov    (%rax),%rax 
   0x55cd3ef03702 GenericDevice::updateValue()+106  mov   -0x38(%rbp),%rdx
   0x55cd3ef03706 GenericDevice::updateValue()+110  mov   %rdx,%rdi         
   0x55cd3ef03709 GenericDevice::updateValue()+113  callq  *%rax
=> 0x55cd3ef0370e GenericDevice::updateValue()+118  ....

Voyez-vous ce marqueur ?

Si ce n'est pas le cas, votre crash est probablement ailleurs (mais bon travail d'analyse de vos données).

Si vous voyez le marqueur, d'autres détails, tels que le modèle et la marque exacts du processeur peuvent être importants (voir par exemple cette question et réponse).

1voto

TLepold Points 57

La déclaration d'appel (call statement) pousse l'adresse de retour sur la pile avant d'exécuter la fonction appelée. Source Intel® 64 and IA-32 Architectures Software Developer’s Manual page 225. Un autre thread maintenait une référence invalide à une variable sur la même pile et l'a décrémentée, ce qui était l'adresse de retour stockée. Fondamentalement, le thread était censé maintenir une référence à un compteur comptant combien de tâches de GenericDevice::updateValue() étaient encore en attente. En cas de dépassement de temps, le compteur sortirait de la portée mais le thread en cours d'exécution conservait toujours la référence désormais invalide. Le dépassement de temps se produisait rarement et uniquement avec des périphériques de lecture au lieu de maquettes. Ainsi, l'adresse de retour stockée sur la pile était occasionnellement corrompue.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X