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++

  1. #1
    Membre expérimenté
    Profil pro
    Inscrit en
    Février 2004
    Messages
    1 824
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 1 824
    Points : 1 544
    Points
    1 544
    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
    "Heureusement qu'il y avait mon nez, sinon je l'aurais pris en pleine gueule" Walter Spanghero

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

    Informations forums :
    Inscription : Février 2004
    Messages : 1 824
    Points : 1 544
    Points
    1 544
    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)
    "Heureusement qu'il y avait mon nez, sinon je l'aurais pris en pleine gueule" Walter Spanghero

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    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 éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 069
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

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

    Informations forums :
    Inscription : Février 2005
    Messages : 5 069
    Points : 12 113
    Points
    12 113
    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 expérimenté
    Profil pro
    Inscrit en
    Février 2004
    Messages
    1 824
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 1 824
    Points : 1 544
    Points
    1 544
    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é
    "Heureusement qu'il y avait mon nez, sinon je l'aurais pris en pleine gueule" Walter Spanghero

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    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

  7. #7
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Autre chose, as-tu activé un maximum de warning de compilation?
    Si oui, en as-tu corrigé par un cast (ce qui est souvent la mauvaise solution)?
    En as-tu masqué dans ton code?
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  8. #8
    Membre expérimenté
    Profil pro
    Inscrit en
    Février 2004
    Messages
    1 824
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 1 824
    Points : 1 544
    Points
    1 544
    Par défaut
    Ha j'ai peut-être trouvé quelque chose.

    Dans les data on ne deal qu'avec des buffers, un peu comme ce que l'on fait en 3D pour remplir les buffers de la carte graphique :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    // A l'initialisation :
    void* pData = malloc(uiSize * sizeof(SProperty<X>::TType));
     
    // Récupération :
    void* ret = (void*)((long)pData + (iIndex * sizeof(SProperty<X>::TType)));
    C'est ce (long) sur pData qui me taraude sur une possible différence entre 64 bits et 32 bits.

    Et on fait pareil pour le set (memcpy((long)pData + ......)

    Ça ne me paraît pas top. Vous en pensez quoi (à part le static_cast<long> que je m'empresse d'utiliser) ?

    On va y arriver

    En tout cas merci pour votre aide !

    [Edit] En plus on connait le type de pData, je vais modifier en ((static_cast<SProperty<X>::TType*>(pData))[iIndex]
    "Heureusement qu'il y avait mon nez, sinon je l'aurais pris en pleine gueule" Walter Spanghero

  9. #9
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    Clairement, ton cast en long ici est trop cavalier, la taille d'un long dépend de la machine http://en.cppreference.com/w/cpp/language/types
    Le cast n'est-il pas superflu de toute façon ?

    Aussi, tu devrais favoriser l'utilisation de new et reinterpret_cast
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  10. #10
    Membre expérimenté
    Profil pro
    Inscrit en
    Février 2004
    Messages
    1 824
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 1 824
    Points : 1 544
    Points
    1 544
    Par défaut
    Ok d'acc, je fais ça ce soir, je test et reviens vers vous

    Merci
    "Heureusement qu'il y avait mon nez, sinon je l'aurais pris en pleine gueule" Walter Spanghero

  11. #11
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    En fait, puisque tu décales d'un multiple de la taille du type pointé, la bonne syntaxe, c'est: void* ret = pData + iIndex;Un pointeur désigne est typé pour une très bonne raison: l'adresse d'un bloc mémoire alloué normalement (par la pile ou par malloc) est alignée sur la taille du bloc.
    si p est un pointeur de T, p+1 est aussi un pointeur de T, donc, il désigne le T situé juste après celui désigné par p.


    Et comme ta variable s'appèle ret, je suis prête à parier que tu peux même écrire return pData+iIndex;.

    J'en profite pour raler parce que tu utilises la notation hongroise, qui a le défaut de ne pas être uniforme.
    pData semble être un pointeur, mais p pourrait aussi désigner un paquet réseau, un parametre, ou un POD.
    iIndex est-il un itérateur ou un entier? signé ou non? constant ou non (plutot pour l'itérateur)?
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  12. #12
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par ternel Voir le message
    En fait, puisque tu décales d'un multiple de la taille du type pointé, la bonne syntaxe, c'est: void* ret = pData + iIndex;Un pointeur désigne est typé pour une très bonne raison: l'adresse d'un bloc mémoire alloué normalement (par la pile ou par malloc) est alignée sur la taille du bloc.
    si p est un pointeur de T, p+1 est aussi un pointeur de T, donc, il désigne le T situé juste après celui désigné par p.


    Et comme ta variable s'appèle ret, je suis prête à parier que tu peux même écrire return pData+iIndex;.
    Sauf que pData est un void*, donc ton décallage doit être multiplié par la taille des données et un simple + index ne suffit pas et retournerait le milieu d'un objet si les objets sont plus gros qu'un char.
    Donc soit return reinterpret_cast<SProperty<X>::TType*>(pData) + iIndex; soit return pData + iIndex * sizeof(SProperty<X>::TType);
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  13. #13
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Certes, mais dans ma tête, j'ai fais le raccourci, parce qu'un pointeur vers quelque chose devrait être du type de cette chose.
    Pourquoi pData est-il un void*, s'il doit pointer sur des SProperty<X>::TType?

    Je pencherais plus sur le cast du pointeur, à défaut de le typer correctement, plutot que de reposer sur l'arithmétique d'un pointeur void
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  14. #14
    Membre expérimenté
    Profil pro
    Inscrit en
    Février 2004
    Messages
    1 824
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 1 824
    Points : 1 544
    Points
    1 544
    Par défaut
    Voici ce que j'ai fait :

    - J'ai remplacé le cast (long) + index * sizeof() par le reinterpret_cast<SProperty<X>::TType*>(pData) + index
    - J'ai mis des assert (en vérité des points d'arrêts) concernant l'index demander afin de m'assurer qu'il n'était pas < 0 et >= count

    Sur mon contexte "production", ça a crashé dès le début, j'ai relancé, ça a racrashé encore au début, j'ai relancé et ça a finalement déclenché le traitement qui a crashé au bout de 20 minutes et 35 secondes.

    Les points d'arrêts ne se sont pas déclenchés, donc pas de problème de demande de zone d'accès en dehors des limites allouées.

    C'est un module de calculs combinatoire qui fait beaucoup d'opérations mémoire par seconde et quand ça trouve une meilleure combinaison, ça produit une log disant "j'ai trouvé ci, j'ai trouvé ça, en faisant ci et ça à cet endroit là". En comparant, ça produit les mêmes logs qu'en production.

    Du coup j'ai testé sur un problème plus restreint, donc plus rapide à s'exécuter, et sur deux tests, j'ai un crash au moment du "free", en fin de traitement.

    Il y a donc bien un loup quelque part dans la gestion mémoire, une revue de code en mode remise en question s'impose ;-)
    "Heureusement qu'il y avait mon nez, sinon je l'aurais pris en pleine gueule" Walter Spanghero

  15. #15
    Membre expérimenté
    Profil pro
    Inscrit en
    Février 2004
    Messages
    1 824
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 1 824
    Points : 1 544
    Points
    1 544
    Par défaut
    Citation Envoyé par ternel Voir le message
    Certes, mais dans ma tête, j'ai fais le raccourci, parce qu'un pointeur vers quelque chose devrait être du type de cette chose.
    Pourquoi pData est-il un void*, s'il doit pointer sur des SProperty<X>::TType?

    Je pencherais plus sur le cast du pointeur, à défaut de le typer correctement, plutot que de reposer sur l'arithmétique d'un pointeur void
    Oui c'est con, si on connait le type autant s'en servir, je sais pas pourquoi j'ai fait ça à l'époque
    "Heureusement qu'il y avait mon nez, sinon je l'aurais pris en pleine gueule" Walter Spanghero

  16. #16
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    A ce niveau-là, autant passer à un std::unique_ptr (pour la désallocation) de std::array (pour la tabularité).
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  17. #17
    Membre expérimenté
    Profil pro
    Inscrit en
    Février 2004
    Messages
    1 824
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 1 824
    Points : 1 544
    Points
    1 544
    Par défaut
    Oui pourquoi pas,

    Si ce couple std::unique_ptr / std::array ne consomme pas plus de CPU pour s'auto gérer et s'utiliser en random access, c'est effectivement envisageable.

    J'ai nettoyé à coups de reinterpret_cast<>[index] partout. Depuis je n'ai plus eu aucun crash en début d'exécution, c'est bon signe.

    Parfois ça crash au niveau des "free()" en fin de traitement, de manière aléatoire sur le nettoyage de telle ou telle data, et sur un cas il n'y a pas eu de problème.

    J'aimerais bien dégager ces malloc / free pour aller vers du new[] / delete[], mais comment lorsque la taille du tableau à allouer n'est connue qu'à l'exécution ?
    "Heureusement qu'il y avait mon nez, sinon je l'aurais pris en pleine gueule" Walter Spanghero

  18. #18
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 965
    Points
    32 965
    Billets dans le blog
    4
    Par défaut
    std::vector
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  19. #19
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    En ce cas, tu peux passer par std::vector, et un appel à sa fonction reserve.

    Comme vector est susceptible de faire une réallocation si la taille est insuffisante pour une insertion, ce n'est pas forcément la solution idéale.

    Il te faut apparemment répondre à plusieurs contraintes:
    Une seule allocation, à un moment précis.
    Une taille maximale fixe, mais inconnue à la compilation.
    Une allocation hors de la pile (sur le tas, donc).
    L'espace d'allocation est ni copiable ni déplaçable.

    Cela ressemble aux problématiques des allocateurs.
    Peut-être devrais-tu utiliser utiliser std::vector<int, super_allocator>.

    Ton allocateur te fournirait l'accès à ta zone mémoire préallouée, en simulant gérant les allocations dedans.
    Renseigne-toi aussi sur ce que sont les placement new et placement delete. (Bidule* = new (adresse) Bidule();)
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  20. #20
    Membre expérimenté
    Profil pro
    Inscrit en
    Février 2004
    Messages
    1 824
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 1 824
    Points : 1 544
    Points
    1 544
    Par défaut
    Peu n'importe le contenant, l'important c'est le contenu ^^

    On verra plus tard les array / vector mais je ne pense pas que ce soit une bonne solution dans mon contexte dans la mesure il la gestion mémoire conserve les opérations effectuées afin de revenir en arrière plus tard (des espèces de rollback). En cas de réallocation par un conteneur, je n'ai pas les moyens de mettre à jour les adresses pointées et stockées pour revenir en arrière plus tard, et encore moins si je ne sais pas lorsqu'elles ont changées.

    C'est pourquoi je n'utilise plus de realloc. Dans un environnement dédié ça fonctionnait car les adresses retournées étaient toujours les mêmes, mais lorsque je suis passé sous conteneur Docker, ça changeait et je n'avais pas, et n'ai toujours pas, la gestion nécessaire pour intégrer ces changements d'adresses pointées.

    Toujours est-il que ça a fait avancer quelque chose le fait d'utiliser des accès tableau "traditionnel" plutôt que des "cast en long + des sizeof() * index" ainsi que des "value =" plutôt que des "memcpy" avec des reinterpret_cast<>[index].

    En effet :
    - Plus aucun plantage sous Windows en debug en tout début de traitement (au moment où on a alloué sans utiliser)
    - Le contexte "mini moi" passe jusqu'au nettoyage de la mémoire en fin de traitement, même en relançant le traitement plusieurs fois sans réinitialiser le service
    - Les résultats sont les mêmes en Debug / Windows 32 bits qu'en prod sous Docker / Release / Linux / 64 bits avec et sans le nettoyage concernant l'accès des zones mémoire

    En revanche sur le vrai contexte, ça plante toujours au bout de 20 - 25 minutes en mode debug sous Win32.

    C'est peut-être dû à des limites matérielles ?
    "Heureusement qu'il y avait mon nez, sinon je l'aurais pris en pleine gueule" Walter Spanghero

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

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