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.
-
Le pointeur this de l'objet est utilisé
(gdb) x /g $rbp-0x38 0x7ffe39086e28: 0x000055cd40586698 (gdb) p this $1 = (GenericDevice * const) 0x55cd40586698
-
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):
-
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.
-
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 $RIP0x55cd3ef0370a 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.