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

C++ Discussion :

Comment détecter une incompatibilité binaire avant de lier dynamiquement une bibliothèque ?


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé
    Homme Profil pro
    Ingénieur validation
    Inscrit en
    Août 2018
    Messages
    42
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Côtes d'Armor (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur validation
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2018
    Messages : 42
    Par défaut Comment détecter une incompatibilité binaire avant de lier dynamiquement une bibliothèque ?
    Bonjour

    Je viens de passer un peu (trop) de temps à investiguer un bug qui causait une segmentation fault.

    Sur un projet un peu volumineux où le code est construit par module sous la forme de bibliothèques liées dynamiquement, j'ai une lib qui expose une classe Foo, avec un constructeur sous la forme :

    Foo::Foo(std::shared_ptr<Bar> bar){
    // ...
    }

    Bar étant une classe abstraite. C'est l'exécutable qui possède une classe dérivée de Bar et fournit l'implémentation.
    La segfault se produit lorsque l'on tente d'accéder à un champ de Bar.
    bar->champBar; // crash

    Je n'avais pas de crash si je tentais d'accéder à une fonction virtuelle. Pas de crash non plus si le constructeur était inliné dans le header Foo.h.
    Dans un premier temps, j'ai cru à un problème de slice, et j'ai cherché à débusquer un appel implicite à un constructeur de copie, en vain.

    Finalement, il s'est avéré que lors du build de la lib et de l'exécutable, une des dépendances de Bar n'était pas configurée de la même façon, causant une incompatibilité binaire entre l'interface et l'implémentation.

    Existe-t-il un moyen de mettre en évidence ce type de problème lors de l'édition des liens (j'utilise gcc et la configuration de build est gérée sous CMake) ? Ou au moins un outil d'analyse des binaires qui pourrait relever le problème avant que l'appli ne crashe lamentablement à l'exécution ?

  2. #2
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 502
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 502
    Par défaut
    Vous avez eu de la chance jusqu'à présent, et vous avez encore de la chance en ayant des segfault au moment de l'accès et pas des erreurs ailleurs, bien après et dans du code qui n'a rien à voir.
    (Utilisation d'options de build avec utilisation massive de canaries en Debug ?)
    Votre application était tombée en marche jusque-là.

    Il n'y a pas d'ABI (standardisation du binaire) en C++, contrairement au C.

    Il est donc complètement illusoire et dangereux d'utiliser des classes/fonctions manglées C++ dans les API entre modules binaires (EXE et DLL) ; mais avec les librairies statiques, il faut généralement faire la génération des .lib avec les mêmes options que son client : utilisation des concepts de projets de votre IDE préféré.

    Pour les problèmes de compatibilité "structurelle" comme la version de la C-RUNTIME, le code dans les headers vérifie la présence et la valeur de certaines constantes de compilation pour détecter l'utilisation de C-RUNTIME différentes/incompatibles avec les options "locale" du fichier à la compilation.
    Les informations qui passent entre le compilateur de M$ (cl.exe) et l'éditeur de lien de M$ (link.exe) permettent aussi de détecter ces mélanges au moment de l'édition de lien. (via les .lib généré mais aussi bien d'autres fichiers (.pdb, etc...))

    Mais si vous avez une structuration en couche cohérente avec les dépendances, et que vous compilez bien TOUS (.lib, .dll, .exe, .ocx, etc...) (tout ce qui est lié à ce qui a changé, donc la définition de l'interface abstraite est en source de dépendance de la lib et le client de la lib et dépendante de la lib : compilation de la couche de l'interface abstraite + compilation de la lib + compilation du client de la lib ) avec le même compilateur ET les mêmes options, vous ne devriez pas avoir ce type de problème. (Mais on perd beaucoup de l'intérêt du découplage en couche)

    Mais une API "objet" pour une Dll, c'est caca.

  3. #3
    Membre Expert
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    668
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 95
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 668
    Par défaut
    Salut,

    Citation Envoyé par Grool Voir le message
    le code est construit par module sous la forme de bibliothèques liées dynamiquement, j'ai une lib qui expose une classe Foo, avec un constructeur
    ... une classe abstraite ... l'exécutable ... fournit l'implémentation.
    Dans quel intérêt tu fais ça ?
    Si je ne m'abuse, en programmation modulaire on s'arrange pour avoir un couplage faible, dans ton cas, il me semble carrément circulaire.
    Sauf les tests unitaires, je ne vois pas comment tu peux débusquer tes problèmes d'incompatibilités.
    Et sinon, à part le PImpl, regarde du côté de la communication interprocessus. Il y a par exemple la programmation orientée composant qui n'est pas loin de ce que vous faites, mais qui offre déjà plus de stabilité.

  4. #4
    Membre éclairé
    Homme Profil pro
    Ingénieur validation
    Inscrit en
    Août 2018
    Messages
    42
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Côtes d'Armor (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur validation
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2018
    Messages : 42
    Par défaut
    Bonjour

    Merci pour les réponses.

    Alors, pour donner un peu plus de détails.
    C'est justement les tests qui se vautraient en segfault. Et c'est en particulier un bouchonnage qui modifie une dépendance.

    Cependant, ce problème peut être plus général et arriver sur l'appli si jamais une erreur se glisse dans les scripts CMake.

    Je ne connais pas l'historique des choix d'archi, mais je ne vois pas trop l'intérêt des liens dynamiques. Je vais tenter de tout repasser en statique.

  5. #5
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 502
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 502
    Par défaut
    Comme l'indique @kaitlyn, il semble avoir des dépendances circulaire, ce qu'il ne devrait jamais arriver.
    Mais qui arrive si on ne fait pas une architecture en couche + conception soignée des dépendances entre les modules de chaque couche.
    On n'a l'impression que l'exécutable, donc la couche la plus haut, défini un truc qui est utilisé par une lib, qui naturellement est une couche plus basse que l'exe.
    C'est le genre de cochonnerie qui passe "inaperçu" (quand on ne fait pas de refactoring "sérieux") quand on ne fait que des librairies statiques.
    Car tout est lié en un coup à la fin.
    Donc votre approche de tout mettre en statique, c'est un peu la tactique de la tête de l'autruche dans le sable, c'est pas très pérenne.

    Que vous ayez réussi à faire ce genre de truc avec des Dll, c'est bien plus complexe.
    Mais j'ai l'impression que le passage de tout statique à un peu de dynamique est récent et que le "problème" que vous venez de vous prendre, c'est juste les conséquences de problèmes bien plus en amont.
    Là, vous essayez de casser le thermomètre pour soigner votre fièvre.

    Je ne connais pas l'historique des choix d'archi, mais je ne vois pas trop l'intérêt des liens dynamiques.
    Par exemple, détecter les architectures foireuses.

    La classe abstraite doit être définie et implémentée dans une librairie, qui sera utilisé par l'exécutable et la librairie dynamique, donc plus de cycle à la con.
    Comme il n'y aura pas de cycle, les outils de génération de projet type CMake devraient faire du bien meilleur boulot.

    Après, c'est peut-être que vous vous êtes mal exprimé et que la conception n'a pas vraiment de dépendance cyclique (parce que c'est vraiment un piège pour très très débutant).

    ...mais je ne vois pas trop l'intérêt des liens dynamiques.
    Plus sérieusement, ç'est très variable en fonction de l'OS et du projet, mais, en général :
    -lancement de l'application plus rapide
    -plus de flexibilité sur des configurations machines et OS hétérogènes
    -correction "automatique" de bugs ou de faille de sécurité
    -économie d'espace disque
    -gestion des versions plus simple (quand c'est bien fait, sinon, c'est un enfer encore plus sombre)
    -etc...
    (et sous Windows, vous n'avez pas le choix, les API de l'OS sont dans des Dll)

    un bouchonnage qui modifie une dépendance.
    Ca sent pas l'architecture bien carrée à base de TDD tout ça.

    Existe-t-il un moyen de mettre en évidence ce type de problème lors de l'édition des liens
    Si vous n'utilisez que des Dll et que vous faites une compilation depuis rien (pour éviter d'utiliser des .lib qui traînent), le linker devrait gueuler lors de l'utilisation d'un cycle de dépendance, car la génération d'une Dll interdit l'utilisation de choses non implémentées)

    Mais, comme je l'ai déjà écrit, mettre des classes dans les API des Dll, c'est chercher les emmerdes.

  6. #6
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Ouaip, quand on regarde les APIs C++ côté Microsoft, c'est soit:
    • La libc etc., intrinsèquement dépendante du compilateur (et encore, plus récemment ils se sont débrouillés pour avoir un truc un peu plus souple)
    • Microsoft Foundation Classes, dans le même cas
    • Component Object Model, qui utilise une ABI standardisée (et utilisable depuis le C) justement pour éviter ce genre de problèmes.
    • ou des APIs C++ entièrement exposées via des templates ou des classes inline, qui appellent la DLL via l'ABI C (comme GDI+)
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  7. #7
    Membre éclairé
    Homme Profil pro
    Ingénieur validation
    Inscrit en
    Août 2018
    Messages
    42
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Côtes d'Armor (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur validation
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2018
    Messages : 42
    Par défaut
    Je suis sous Linux.

    Je peux maîtriser le code de l'exécutable et des libs. Ce découpage n'est donc qu'un choix d'architecture. Une appli monolithique aurait pu être un autre choix, avec le risque d'en faire un plat de spaghettis.

    Donc votre approche de tout mettre en statique, c'est un peu la tactique de la tête de l'autruche dans le sable, c'est pas très pérenne.
    Argh, mon but était plutôt de faire gueuler l'éditeur de liens.

    Mais j'ai l'impression que le passage de tout statique à un peu de dynamique est récent et que le "problème" que vous venez de vous prendre, c'est juste les conséquences de problèmes bien plus en amont.
    Ce qui est récent, c'est l'accès direct à un champ. Auparavant, il y avait un accesseur virtuel. Et là pas de problème, on travaille avec une fonction de l'instance réelle qui connait le mappage de ses champs.

    Ca sent pas l'architecture bien carrée à base de TDD tout ça.
    Non, non, en effet

    Globalement, l'archi est :
    un exécutable utilise un module sous la forme d'une lib .so.
    Ce module dialogue lui-même avec d'autres modules. La classe abstraite regroupe tous les comportements communs de ces communications. C'est une sorte de client.

    Alors maintenant, la question est pourquoi est-ce l'exécutable qui fournit les implémentations ? Il ne devrait même pas avoir connaissance du découpage des modules et de leurs interactions.
    Si j'ai bien compris l'intention du gars qui a mis ça en place, il y a trouvé un moyen commode de substituer les clients par des stubs dans l'exécutable de test.
    Ce qu'il a oublié, c'est que la classe abstraite repose sur des éléments de plus bas niveau (des signaux sigc) eux-mêmes bouchonnés lors du build de l'exécutable de test. La lib est par contre construite de la même façon dans toutes les configurations.

    Le but de mon fil de discussion n'est pas tellement de trouver la faille de conception, mais bien de trouver un moyen de lever un warning comme la situation se présente. Car, à mon grand désarroi les cours de C++ passent trop de temps à présenter des syntaxes qu'on utilisera jamais plutôt que de parler des règles fondamentales d'architecture.

    Il me vient un exemple que j'aurais pu naïvement programmer et qui m'aurait provoqué le même type de souci :
    Un exécutable utilise une lib de code réutilisable en lui fournissant un objet dont la fonction est de gérer des callbacks. Chaque exécutable qui utilise la lib fournit sa propre implémentation. La lib appelle les callbacks selon les règles fixées par l'interface mais leur comportement est spécifique à chaque exécutable.
    Avec une lib qui a une interface en C, il faudrait que l'exécutable envoie des pointeurs de fonctions libres ou statiques pour chaque callback, sans pouvoir tout regrouper dans une interface unique et claire.

    Cela dit, je suis d'accord, une lib dynamique qui expose des classes en C++ est un nid à emmerdes. Déjà, parce qu'il n'y pas de norme pour le nommage des fonctions membres et les surcharges.
    Par le passé, j'avais essayé d'utiliser la dll d'OracleDB avec MinGW, mais compatible seulement avec Visual C++.

Discussions similaires

  1. Comment détecter une SD Card?
    Par sfaxi dans le forum C++
    Réponses: 0
    Dernier message: 14/04/2008, 18h34
  2. Réponses: 3
    Dernier message: 10/07/2007, 13h53
  3. Comment détecter une modification ?
    Par programaniac dans le forum Composants VCL
    Réponses: 5
    Dernier message: 16/11/2005, 13h25
  4. Comment détecter une erreur dans un process
    Par chuckboy dans le forum MFC
    Réponses: 3
    Dernier message: 25/10/2005, 10h40
  5. [VCL] Comment détecter une combinaison de touches ?
    Par micatmidog dans le forum Composants VCL
    Réponses: 3
    Dernier message: 23/01/2005, 14h19

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