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 :

Déboguer une DLL utilisée par un module NodeJS natif avec VS


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éprouvé
    Profil pro
    Inscrit en
    Février 2004
    Messages
    1 825
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 1 825
    Par défaut Déboguer une DLL utilisée par un module NodeJS natif avec VS
    Bonjour à tous,

    Je travaille sur un sujet concernant de l'optimisation combinatoire. Mon environnement de production est Linux (Ubuntu) mais pour le dev je privilégie Windows et VisualStudio pour les facilités de débogage, une phase importante dans ce type de sujet où l'on ne peut pas s'en sortir à coups de simple logs.

    J'ai essayé code::blocs mais je ne sais pas si c'est parce que ça tournait dans une VM Ubuntu Desktop ou quoi, en tout cas l'expérience s'est avérée négative (IHM qui déconne, impossibilité de déboguer ce qui se passait dans les threads, etc.).

    J'ai également essayé l'addon Visual Studio permettant de déboguer à distance avec gdbserver en s'attachant au process distant en question. Ça fonctionne bien, très bien même, mais on ne retrouve pas l'aisance nécessaire (pouvoir mettre en pause à tout moment, les asserts ne sont pas catchés, impossibilité d'arrêter une compilation cours, d'activer un point d'arrêt en cours d'exécution etc.).

    Voici mon archi pour cette partie là :
    - Une DLL C++
    - Chargée par un module natif Node à coups de LoadLibrary sous Win32 et dlopen sous Linux
    - Une exposition HTTP via ExpressJS
    - Une application Web qui consomme l'API HTTP

    Le but étant de pouvoir générer la DLL et la déboguer dans MSVC après avoir lancé le traitement sur l'interface WEB.

    La DLL utilise la bibliothèque jsoncpp et pour une raison que j'ignore, ça ne fonctionne pas en x64 (une assertion est levée concernant un check sur du max int value). Je n'ai pas cherché plus loin et pour du debug, j'ai tout claqué en 32bits (version de NodeJS, compile jsoncpp, compile module node natif et compile DLL, tout en 32bits).

    Dans les options de débogage VS :
    - Command : C:\Program Files (x86)\nodejs\node.exe
    - Working Directory : C:\dev\mon-service-node
    - Command Arguments : bin\www (le point d'entrée par défaut d'un service Express)

    Je lance le débogage, je lance le traitement, le débogueur est activé et le traitement tourne, je peux le mettre en pause, mettre les points d'arrêt quand je veux, tout est nickel.

    Jusqu'à un moment où je me prends des erreurs de violation d'accès mémoire, de manière aléatoire, de type aléatoire. Certaines arrivent en tout début d'exécution, je relance le tout et ça passe, sans rien changer au code.

    J'ai eu ceci :
    Unhandled exception at 0x06C1635F in node.exe: 0xC0000005: Access violation reading location 0x7BD7A5CC
    Exception thrown at 0x0F4B3CC0 (ucrtbased.dll) in node.exe: 0xC0000005: Access violation writing location 0x06913FC4.
    C'est pas très courant comme organisation mais quelqu'un saurait me dépanner pour obtenir un schéma fiable ? Peut-être y a-t-il des options à configurer dans les différentes compilation ? Actuellement tout est compilé en '/MTd'.

    Il faut savoir que ma mémoire (8Go) dépasse souvent les 7,5Go utilisés et que le traitement agit beaucoup sur la mémoire et en débug peut durer plus de 20 minutes, peut-être y a-t-il des trucs liés à la fragmentation ?

    Toujours est-il que sous Linux, ça tourne jusqu'au bout sans la moindre erreur.

    Merci par avance,

    A bientôt

  2. #2
    Membre éprouvé
    Profil pro
    Inscrit en
    Février 2004
    Messages
    1 825
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 1 825
    Par défaut
    Une autre :

    Exception thrown at 0x0F9D843F (Algo.dll) in node.exe: 0xC0000005: Access violation reading location 0x07A1F018.
    Après 20min44 de session debug (sans arrêt)

  3. #3
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    De manière génrale, ce genre de bug aléatoire est le plus souvent du, en C++, à une tentative de déréférencement d'un pointeur invalide.

    Plusieurs raisons peuvent faire qu'un pointeur est invalide:
    • il vaut explicitement nullptr (mais il est alors connu pour représenter une adresse invalide, et on peut donc placer une assertion sur le fait qu'il doit valoir "autre chose")
    • l'adresse mémoire représentée par le pointeur a été libérée, mais la valeur du pointeur n'a pas été remise à nullptr
    • un mauvais transtypage faisant que l'on a un pointeur connu pour pointer sur une donnée de type Type1 alors que c'est une donnée de type AutreType qui se trouve à cet endroit,
    • d'autres raisons encore qui ne me viennent pas forcément à l'esprit maintenant.

    Pour ce qui est des problèmes de transtypage, il n'y a qu'une solution: renonce définitivement à toute pratique ayant recours au (pseudo) RTTI: Si tu as été "assez bête" (ou peut-être simplement forcé) de perdre le type réel (dynamique) d'une donnée lorsque tu en as pris l'adresse, tu dois mettre en oeuvre des pratiques basées sur les comportements polymorphes (les fonctions virtuelles) et le double dispatch pour travailler avec le type dynamique.

    Ce n'est pas (loin s'en faut) une technique exempte de problème, mais le patron de conception visiteur est un exemple de ce que l'on peut faire dans ce cas là.

    Si tu respecte correctement le LSP, cette décision te permettra en outre d'améliorer le respect que tu as de l'OCP, ce qui ne pourra qu'améliorer la qualité générale de ton projet; tant en termes de fiabilité qu'en terme d'évolutivité ou de facilité à la compréhension .

    Pour le reste, je présumes que tu utilises des pointeurs nus... Ce qui est une très mauvaise idée:

    Depuis l'arrivée de C++11, nous disposons dans la bibliothèque standard de pointeurs intelligents, qui sont d'ailleurs basés exactement sur le modèle de ceux de boost. Le compilateur de Visual Studio 2017 supporte parfaitement non seulement C++11, mais aussi C++14 (et certains aspects de C++17), alors que celui Visual Studio 2015 ne supportait pas l'intégralité de C++11 (il avait, par exemple, un problème avec les fonction deleted et defaulted).

    Peut être devrais tu envisager de mettre ton code "a jour" au vu de ces nouvelles normes. Tu verras, les fonctionnalités qu'elles fournissent te rendront la vie de développeur bien plus belle

    Si, pour une raison ou une autre, tu ne devais pas pouvoir disposer des fonctionnalités des normes modernes (C++11 et ultérieures), essaye au moins de faire en sorte que les classes qui manipulent des ressources (comme les pointeurs) soit des "capsules RAII":
    1. Chaque élément dans ce cas ne doit avoir la responsabilité que d'une et une seule ressource: C'est lui qui décide quant il faut la créer, mais c'est aussi lui qui veille à ce que la ressource soit correctement détruite lorsqu'il est lui-même détruit
    2. Pour les éléments ayant sémantique d'entité, veille à en interdire la copie et l'affectation en plaçant le constructeur de copie et l'opérateur d'affectation dans l'accessibilité privée sans les implémenter
    3. Pout les éléments ayant sémantique de valeur, veille à respecter la règle des trois grands: si, pour une raison ou une autre, tu en viens à fournir une implémentation personnelle du constructeur de copie, de l'opérateur d'affectation ou du destructeur, tu dois veiller à fournir l'implémentation des trois. Penses, pour t'y aider, à la forme canonique orthodoxe de Coplien . Un idome régulièrement utilisé dans ce cas est l'idiome copy and swap.
    4. Enfin, veille toujours à vérifier -- de préférence au travers d'une assertion -- que tous les pointeurs que tu manipulent ne sont pas égaux à nullptr. Si tel est le cas, il s'agit d'une erreur de logique qui mérite d'être corrigée avant la mise en prod
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  4. #4
    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
    En plus des conseils de @koala01, que j'approuve sans réserve, je vais faire des remarques plus "tactiques" (court terme).
    Bon, même si cela n'a pas été la première solution envisagée, vous êtes arrivé à une configuration qui marchouille, c'est bien.
    Un truc qui peut posé pas mal de problèmes erratiques, c'est de mélanger des Runtime Release et des Runtime Debug.
    "node.exe" utilise une C-Runtime Release et votre Dll une C-Runtime Debug, si l'API utilisé n'est pas étanche d'un point de vue mémoire, ça va pas trop le faire.
    Je vous conseille donc de compiler avec les informations de Debug (les fichiers .pdb) même en Release, et d'utiliser la version Release durant vos sessions de débugging.
    C'est un tout petit peu moins confortable qu'en Debug, mais c'est encore pas mal.
    De plus, si l'API n'est pas étanche et que "node.exe" n'utilise pas la même C-Runtime (compilé avec un autre compilateur), même en Release, ça risque aussi de partir en sucette.
    Par étanchéité, c'est le fait que l'allocation, la libération et les déplacements d'une zone mémoire soient exécutés par le même module binaire (dll, exe).
    Après, si le problème est bien un problème d’étanchéité de l'API, vous devriez commencer par la rendre étanche.

    Toujours est-il que sous Linux, ça tourne jusqu'au bout sans la moindre erreur.
    Sous Linux, la C-Runtime est commune à tout les exécutables, je crois, donc des API C qui fuite, ça passe.
    Mais je crois que le problème se posera aussi sous Linux si l'API est C++, car je crois que la C++-Runtime n'est pas commune même sous Linux.

    Jusqu'à un moment où je me prends des erreurs de violation d'accès mémoire, de manière aléatoire, de type aléatoire. Certaines arrivent en tout début d'exécution, je relance le tout et ça passe, sans rien changer au code.
    C'est un heisenbug.
    Votre programme ne tourne pas dans le cosmos soumis au rayonnement ionisant du soleil. S'il plante c'est qu'il est buggé.
    Quand ça plante, analysez le problème. Le débogueur donne tous les outils pour comprendre pourquoi c'est parti en sucette, même si ce n'est pas systématique.

  5. #5
    Membre éprouvé
    Profil pro
    Inscrit en
    Février 2004
    Messages
    1 825
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 1 825
    Par défaut
    Merci pour vos réponses,

    A vrai dire il n'y a pas d'héritage dans ce projet, ni d'allocation dynamique durant l'exécution des algorithmes.

    Au début de l'utilisation, on alloue tout ce dont on a besoin ce qui constitue le terrain de jeu mémoire, et en fin d'exécution on release tout.

    On utilise le C++ et la méta programmation pour gagner un max de temps à l'exécution et les types manipulés ne sont que des types primitifs, des tableaux et des boost_datetime.

    On n'utilise pas non plus les RTTI, je déteste ça, c'est "dégueulôsssse"

    J'ai encore regardé et il n'y a aucune allocation dans le module node dont la data est transmise à la DLL, ni dans la DLL pour recevoir des data dans le module node, tout est passé en primitive, aucune classe, aucune structure.

    Cela n'exclu pas un bug, j'ai toujours toujours des doutes sur mes productions, d'où le blindage de TU qui dépassent le cadre purement fonctionnel. En fin d'utilisation du service il y a un test d'intégrité pour valider que tous les indexes références les bonnes data, rien à -1 alors que ça ne devrait pas l'être.

    J'ai déjà eu des soucis logiques qui m'ont donné des erreurs "segmentation falt" lorsque j’accédais en lecture ou écriture en dehors de la zone mémoire qui m'était allouée. Ça ne crashait pas durant l'exécution mais une fois qu'on releasait la mémoire allouée, donc ça se voit tout de suite.

    Peut-être qu'il y a une portion de code entre des #ifdef WIN32 qui sont buguées, ce peut être une piste également mais elles sont très peu nombreuse ces portions et ne font rien si ce n'est inclure un fichier ou un autre ou utiliser une fonction ou une autre (LoadLibrary VS dlopen).

    En revanche je commencerais bien par chercher à être sûr du mélange des modes de compilation, et n'ai pas vraiment la main sur les options par défaut que met node-gyp dans le fichier projet généré avant d'appeler msbuild.

    Lorsqu'il y a une brique qui utilise un autre runtime qu'une autre brique, j'ai des erreurs me l'indiquant, mais peut-être que d'autres options sont "incompatibles" ?

    Je vais essayer de tout compiler en release avec les symboles de debug voir ce que ça donne mais si il faut ça reviendra aux mêmes possibilités de debug que j'ai actuellement quand je fais avec linux via gdbserver.

    C'est contrariant en tout cas :'(

    Merci encore,

    A bientôt

    [Edit] Info peut-être utile : Jusqu'il n'y a pas longtemps je pouvais faire tourner le moteur sous Windows dans la mesure où il possédait sont propre serveur HTTP embarqué en utilisant PION Network Library), mais depuis on a préféré faire porter cette couche là par NodeJS / Express car PION devenait non supporté

  6. #6
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par mister3957 Voir le message
    Merci pour vos réponses,

    A vrai dire il n'y a pas d'héritage dans ce projet, ni d'allocation dynamique durant l'exécution des algorithmes.

    Au début de l'utilisation, on alloue tout ce dont on a besoin ce qui constitue le terrain de jeu mémoire, et en fin d'exécution on release tout.
    Il y a donc bel et bien une allocation dynamique de la mémoire "quelque part", et une libération de celle-ci "ailleurs".

    Pourrais déjà nous montrer au minimum les fonctions qui s'occupent de ces deux parties

    Mais bon, si tu a la certitude que les pointeurs ne sont pas en cause, voici d'autres pistes de réflexion

    1- Vérifie tes conversions, surtout si tu fais des transtypages "sauvages" (par exemple, de short vesr un int ). Si tu en fais, utilise de préférence static_cast au transtypage "C style" ( (int)monShort; )

    2- Méfies toi des conversion de valeurs signées en valeurs non signées (et inversement)

    3- Surveilles tes indices, et assures toi en permanence (à l'aide d'assertion, par exemple), que tu n'essayes pas d'aller "une case trop loin"

    Il y en a surement d'autres, mais elles me viendront à l'esprit "plus tard"
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. TTimer dans une dll utilisée par une application .net
    Par Pascale38 dans le forum C++Builder
    Réponses: 16
    Dernier message: 12/07/2017, 16h18
  2. Réponses: 4
    Dernier message: 02/12/2011, 14h52
  3. Réponses: 0
    Dernier message: 15/04/2010, 12h34
  4. Réponses: 3
    Dernier message: 03/09/2008, 15h09
  5. Utilisation d'une dll native par une toolbar managée
    Par didierll dans le forum C++/CLI
    Réponses: 1
    Dernier message: 10/07/2007, 07h56

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