Forum Programmation.autre débugguer un CGI avec GDB ( sous apache2 )

Posté par . Licence CC by-sa
Tags : aucun
2
11
mar.
2014

J'ai un cgi qui bug à l'exécution via apache, mais exécuté indépendamment fonctionne sans souci ( avec bien entendu un $export QUERY_STRING avant de lancer l'exécutable ).

Du coup, vu que je ne suis pas vraiment à l'aise avec les erreurs d'apache*, j'ai pensé à utiliser GDB histoire de comprendre le fin mot de cette histoire, mais je ne vois absolument pas comment je pourrais faire ça: apache est lancé par root, c'est un daemon et multi-threadé en plus je suppose.

Hors, même si lancer gdb en ligne de commande ne me pose plus aucun souci depuis quelques temps (quelques => pas beaucoup ), je n'ai encore jamais eu à déboguer ni un daemon, ni un truc multi-thread, et en l'occurrence, il s'agit d'un cgi ( pas multi-threadé, le cgi, hein… ) lancé par un daemon multi-threadé…

Bref, comment faire pour attacher gdb à mon cgi, avec un point d'arrêt automatique à l'entrée?

*:
Les erreurs en question:
"Internal Server Error" ( 500 ) et dans les logs:
[Tue Mar 11 11:00:30.044123 2014] [mpm_prefork:notice] [pid 5463] AH00163: Apache/2.4.7 (Debian) configured—resuming normal operations
[Tue Mar 11 11:00:30.044182 2014] [core:notice] [pid 5463] AH00094: Command line: '/usr/sbin/apache2'
[Tue Mar 11 11:00:42.846120 2014] [cgi:error] [pid 5466] [client 127.0.0.1:57129] AH01215: terminate called without an active exception
[Tue Mar 11 11:00:42.846281 2014] [cgi:error] [pid 5466] [client 127.0.0.1:57129] End of script output before headers: api_compagnon.cgi

Tout ce que je sais, c'est que je dois avoir une merde immonde qui traîne, ça sens le sigsegv ou un truc du genre… ( terminate sans exception active? )

  • # Juste une question : quel est l'intérêt d'un CGI aujourd'hui ?

    Posté par . Évalué à 3.

    Il y a pléthore de langages très bien adaptés à du web interactif ….

    Sinon, si tu as un serveur web multithread et que ton CGI ne gère pas le multithread, ça ne peut pas marcher. Il faut que tu examine chaque fonction utilisée dans ton CGI pour savoir si elle est thread-safe. Beau sac de noeuds en perspective.

    , il s'agit d'un cgi ( pas multi-threadé, le cgi, hein… )

    Comment peux-tu en être certain, vu que le programme qui l'appelle est multithreadé ?

    • [^] # Re: Juste une question : quel est l'intérêt d'un CGI aujourd'hui ?

      Posté par . Évalué à 2.

    • [^] # Re: Juste une question : quel est l'intérêt d'un CGI aujourd'hui ?

      Posté par . Évalué à 3.

      quel est l'intérêt d'un CGI aujourd'hui ?

      Un script php, ou perl, ou shell, ou peu importe le langage, reste du CGI, tant qu'il utilise la RFC en question.
      L'intérêt du CGI, c'est de ne pas avoir à développer un module complet dès que tu veux étendre ton serveur http, ou carrément écrire un serveur dédié. C'est aussi un moyen simple de créer un programme qui peut être exécuté sur n'importe quelle machine ayant un serveur http supportant les CGI, qui sont un standard.

      Il y a pléthore de langages très bien adaptés à du web interactif ….

      Je pourrais apprendre à utiliser un nouveau langage, tel que perl ou python, c'est vrai. Ou supporter l'absence de typage de php. Mais j'ai un impératif de temps aussi.

      Et qui à parlé de web interactif?
      Le programme en question est un webservice: tu lui poses une question, il te réponds. Pas d'état stocké sur serveur ( je n'ose pas parler de REST, car je n'ai pas fini de lire assez de choses à ce sujet, et je risquerais de déraper, mais disons que je tente de m'inspirer de ce que j'en comprend. )
      Donc, la seule chose dont j'ai besoin, c'est de manipuler des chaînes de caractères aisément, chose que le langage que j'ai utilisé fait très bien. Ah, et interroger une DB. Ca aussi, pas de souci.

      Comment peux-tu en être certain, vu que le programme qui l'appelle est multithreadé ?

      Je voulais juste dire par la que le CGI en question n'utilise pas explicitement de thread multiples.

      • [^] # Re: Juste une question : quel est l'intérêt d'un CGI aujourd'hui ?

        Posté par . Évalué à 2.

        C'est aussi un moyen simple de créer un programme qui peut être exécuté sur n'importe quelle machine ayant un serveur http supportant les CGI, qui sont un standard.

        OK, ça se tient.

        Je pourrais apprendre à utiliser un nouveau langage, tel que perl ou python, c'est vrai. Ou supporter l'absence de typage de php. Mais j'ai un impératif de temps aussi.

        Combien de temps vas-tu passer pour débugger ? A mettre dans la balance également. Et juste par curiosité, tu utilises quel langage ?

        Le programme en question est un webservice: tu lui poses une question, il te réponds.

        C'est pas du web interactif ça ?

        Je voulais juste dire par la que le CGI en question n'utilise pas explicitement de thread multiples.

        Dans ce cas il te faut vérifier que tu utilises les bons modules apache, et que as tout configuré pour gérer ce cas de figure (voir un des liens que je t'ai proposé comme piste de départ).

        • [^] # Re: Juste une question : quel est l'intérêt d'un CGI aujourd'hui ?

          Posté par (page perso) . Évalué à 1.

          Le programme en question est un webservice: tu lui poses une question, il te réponds.

          C'est pas du web interactif ça ?

          C'est quoi le web ? (non interactif).
          Tu poses un question et il te répond pas ?

          Matthieu Gautier|irc:starmad

        • [^] # Re: Juste une question : quel est l'intérêt d'un CGI aujourd'hui ?

          Posté par . Évalué à 1.

          Combien de temps vas-tu passer pour débugger ? A mettre dans la balance également.

          En l'occurrence, ce que je débuggue, c'est le couple apache2 + mon CGI. En simulant du CGI normal, c'est à dire en passant les arguments GET via $QUERY_STRING et les arguments POST ( pardon pour les grossiers raccourcis ) via GET, je n'ai aucun problème, ce qui m'a aussi permis de mettre en place des tests unitaires.
          Je ne prétends pas que le bug viens d'apache, juste que j'ai dû rater un détail, et pour le trouver, gdb m'aiderait pas mal.
          Quand ce sera le cas, je ferais un TU pour pas qu'il revienne :)

          Et juste par curiosité, tu utilises quel langage ?

          C++. En utilisant le dernier standard ( C++11 ).
          Peut-être pas ce qu'il y a de plus simple, peut-être, mais dès l'instant que je peux manipuler des chaînes de caractères correctement et facilement ( et le dernier standard aide pas mal pour ça ), avoir un langage avec typage statique ( oui j'y tiens ), me permettant d'utiliser à la fois la POO et la RAII, qui soit standardisé par un organisme qui aime la compatibilité ascendante ( C++ dans 10 ans compilera toujours mon programme, c'est important car il risque effectivement d'être utilisé pendant plusieurs années, sans avoir besoin d'être modifié ) et avec lequel je suis à l'aise… je pense que dans les contraintes que j'ai, ce n'est pas le pire.
          A ce que j'ai lu a divers endroits, python, par exemple pourrais poser des problèmes ( certains se sont plains ici et là de ne pas pouvoir utiliser du code ancien sans devoir le modifier, notamment. Pas acceptable dans mon cas. ).
          D'autres langages, comme php, ne sont pas typés, ou très faiblement, et je veux un truc ou l'outil qui lit mon code me dise que je vais avoir un problème. D'autres encore, genre perl, produisent du code qui, dans mon humble opinion, est peu lisible ( je m'y suis déjà essayé ).
          Je ne dis pas que C++ est la panacée, mais qu'il me semble adapté à mon besoin actuel en fonction de mes contraintes actuelles.

          Sans parler du fait que je préfère éviter de multiplier les technos employées en prod, et qu'a l'heure actuelle, php et C++ sont les 2 seuls langages utilisés.

          C'est pas du web interactif ça ?

          J'imagine que ça dépends de la définition d'interactif. Pour moi, interactif, ça veut dire qu'au cours de l'exécution du programme, l'utilisateur peut envoyer de nouvelles requêtes ( au sens général du terme ) à un logiciel qui se souviens du contexte. Ce qui implique un mécanisme d'identification de l'utilisateur ( pour interagir, on doit savoir identifier son interlocuteur ), par exemple les cookie pour le web.

          Si on faisait le parallèle avec les applications sur terminal, on peut entendre par interactif une application qui pose des questions ( scanf, pour du C ) de façon bloquante ou non ( non bloquant impliquant soit que l'on mets un timeout sur les entrées, soit que l'on joue dans la cours du multi-thread ).
          Genre aptitude, ncmpcpp, apt-get ( pas ncurses, mais il me semble qu'apt-get pose des questions via scanf ) …

          Alors que non interactif serait plutôt les applications qui une fois invoquées ne font que traiter la requête ( en général en utilisant uniquement les arguments passés via ligne de commande, vu que lorsqu'elles lisent l'entrée standard, il arrive qu'elles puissent interagir, comme less ) sans pouvoir être interrompues ( vu que les signaux sont plus une façon de résoudre le problème d'une application trop lente… ou trop bugguée. En général. ).

          Http est prévu, à ce que j'en sais ( cf prochaine parenthèse ), pour ne pas être interactif, mais les cookies, entres autres ( phpssessid aussi, ou un truc du genre? suis mauvais en dev web…) , sont un workaround qui permets de le rendre interactif.

          voir un des liens que je t'ai proposé comme piste de départ

          J'ai commencé à lire tes liens, il y a des chances que ça me mette sur des pistes sympa. La config d'apache n'est vraiment pas simple à gérer à mon niveau malheureusement. ( mais bon, c'est ça qui est en prod… )

          • [^] # Re: Juste une question : quel est l'intérêt d'un CGI aujourd'hui ?

            Posté par . Évalué à 2.

            C'est pas du web interactif ça ?

            J'imagine que ça dépends de la définition d'interactif. Pour moi, interactif, ça veut dire qu'au cours de

            [ … ]

            ( phpssessid aussi, ou un truc du genre? suis mauvais en dev web…) , sont un workaround qui permets de le rendre interactif.

            J'aurais dû mette un smyley, ma remarque n'était pas à prendre au sérieux.

          • [^] # Re: Juste une question : quel est l'intérêt d'un CGI aujourd'hui ?

            Posté par . Évalué à 3.

            et pourquoi passer par apache+cgi
            pourquoi ne pas gerer toi meme le port 80 de ta machine
            puis gerer les ouvertures/dialogues/fermetures ?

            • [^] # Re: Juste une question : quel est l'intérêt d'un CGI aujourd'hui ?

              Posté par . Évalué à 4.

              Et pourquoi utiliser la pile TCP du noyau ? Tu pourrai la recoder aussi…

            • [^] # Re: Juste une question : quel est l'intérêt d'un CGI aujourd'hui ?

              Posté par . Évalué à 1.

              Si je fais un CGI, c'est parce que, au hasard, je ne suis/n'était/ne serait pas le seul à exploiter le port 80, peut-être?

              Sinon, j'aurai utilisé autre chose qu'apache, crois-moi, il y a pas mal de serveurs web, voire http, bien plus légers et qui prétendent être moins pénibles à configurer ( n'ayant pas trop eu le "bonheur" de configurer ce type d'outils, je suis obligé de me fier aux prétentions qu'ils avaient quand j'ai croisé leurs descriptifs dans aptitude ) que le plus célèbre d'entre eux.

  • # process séparé ?

    Posté par . Évalué à 2.

    Si ton CGI est lancé par Apache dans un process séparé, tu pourrais :
    - mettre un sleep dans le main
    - attacher gdb à ce process pendant ce sleep

    Et je mentionne l'autre moyen classique de débugguer : faire des printf partout, pour voir où ça plante. (printf sur la sortie standard, que tu retrouveras dans la réponse HTTP, ou bien dans un fichier de log)

    • [^] # Re: process séparé ?

      Posté par . Évalué à 1.

      J'aime bien l'idée du sleep… pas parfait, mais peut marcher.

      Pour l'autre solution… je préfèrerais éviter, disons que ce n'est pas super souple.

      • [^] # Re: process séparé ?

        Posté par . Évalué à 3.

        C'est ce que je ferais également dans un premier temps.

        Sinon, la solution universelle est de configurer ton environnement et/ou ton serveur Apache pour que ton exécutable produise un fichier core. C'est à cela qu'ils servent. Tu le débugues ensuite à tête reposée dans ton coin. Même si ta machine ne dumpe qu'un seul thread, au moins tu sauras immédiatement où cela se passe.

      • [^] # Re: process séparé ?

        Posté par . Évalué à 2.

        Pour l'autre solution… je préfèrerais éviter, disons que ce n'est pas super souple.

        avec une grosse variable DEBUG=1 en debut de programme
        ou en parametre,

        puis en conditionnant tes printf selon cette variable.

        ainsi tu as un
        export DEBUG=1 tonprogramme
        ou
        tonprogramme --debug

        qui affichera tous les points de passage.

    • [^] # Re: process séparé ?

      Posté par . Évalué à 4.

      • mettre un sleep dans le main

      On peut même mettre une boucle sur un variable qu'on changera avec le debuggeur :

      int debugger_wait=1;
      […]
      
      while (debugger_wait) {
        sleep(1);
      }
      

      Tu verras ton process apparaître lors de la requête, tu t'attaches dessus, tu fais :

      set debugger_wait=0
      

      (j'ai vu cette technique dans OpenL2TP)

      • [^] # Re: process séparé ?

        Posté par . Évalué à 3.

        Bien vu aussi, c'est ce que j'ai fait du coup.

        Cependant, attacher gdb sur apache2 ne suffit pas, parce que, par défaut sur ma debian(*), il y à 6 processus d'apache lancés, et en plus il faut les droits d'admin pour les contrôler (logique).

        Donc, il faut lancer apache2 avec "-X", comme dit sur tant de sites. Sauf que, encore une fois, c'est plus compliqué que ça sur ma machine(*), car juste lancer "#/usr/sbin/apache2 -X" à la main n'exécute pas le contenu du fichier "/etc/apache2/envvars", qui contient un certain nombre de variables d'environnement nécessaire à la configuration d'apache ( qui se trouve elle dans "/etc/apache2/apache.conf" chez moi ).

        Chose que j'ai mis… trop longtemps à voir… bref… il y a une ligne dans ce fameux fichier "/etc/apache2/envvar" qui définit la variable "APACHE_ARGUMENTS".
        C'est ici qu'il faut ajouter le fameux "-X", ce qui donne donc:
        export APACHE_ARGUMENTS=''

        Ensuite, lancer apache, mais pas par son exécutable, ni par le mécanisme d'init comme on l'aurait fait pour d'autres services ( ça semble sûrement évident à certains, mais pour moi qui ne suis pas un admin, j'ai voulu utiliser la méthode classique de debian… grand mal m'a pris. ) mais par l'outil dédié d'apache ( je ne savais pas qu'il y en avait un, à ma décharge… je l'ai découvert via l'auto-complétion. ) via "#/usr/sbin/apache2ctl start".

        Bien entendu, avec la config standard dans /etc, apache utilise le port 80, ce qui implique de devoir lancer cette commande sous root, ou de modifier la config, selon l'humeur et/ou les contraintes.
        Lancer une requête pour le script qui pose problème, taper un petit "ps -A|grep mon_cgi" pour récupérer le pid, et enfin, attacher gdb à ce pid.

        Welcome to your script.

        *:
        Debian testing
        apache 2.4.7
        sysVinit

  • # c'est probablement stupide

    Posté par . Évalué à 4.

    … on ne sait jamais cependant,

    terminate called without an active exception
    End of script output before headers: api_compagnon.cgi

    Moi ce que j'en comprends c'est que ton cgi n'a pas spécialement buggé, il a juste oublié de répondre les headers en-têtes avant le body corps.

    HTTP/1.1 200 OK
    Date: Tue, 11 Mar 2014 13:46:30 GMT
    Content-Length: xxx
    
    content goes here.
    

    Ou peut être plus probablement, fermé la socket avant qu'httpd ne soit prêt à l'accepter.

    Ne pouvant lire ton code, ne connaissant pas les arcanes d'httpd, à toi de nous dire.

    Ceci mis à part, j'en reviens à quelques considérations superflues, mais que je tiens à partager.
    Je rejoint l'avis formulé plus haut que de nombreuses solutions alternatives étaient disponibles.
    A l'écoute de mon expérience et de mes capacités de développements, je n'irais jamais pondre un module apache pour livrer du contenu. AMHA, c'est exposé le projet, aussi petit soit il, à moultes complications supplémentaire sans valeurs ajoutés par rapport à l'objectif du développement initial.
    Quand à la question du time-to-market, à ta place, j'y réfléchirais à deux fois, car l'inexpérience dont tu fais l'illustration ici au regard des arcanes d'httpd, j'ai malheureusement, toutes les bonnes raisons de penser que ton commanditaire va s'en mordre les doigts au plus mauvais moment.
    D'un autre côté, cela ne le regarde que lui et sa décision, sauf si bien sûr, tu fais aussi le suivi du run =D

    • [^] # Re: c'est probablement stupide

      Posté par . Évalué à 3.

      Moi ce que j'en comprends c'est que ton cgi n'a pas spécialement buggé, il a juste oublié de répondre les headers en-têtes avant le body corps.

      Oui mais effectivement, c'est généralement typique d'une segfault du cgi-bin en question. Avec le temps, les serveurs Apache ont fini par prendre en charge ce type d'événement et à l'inscrire dans les logs mais pour avoir travaillé sur le même genre de produits dans les années 2000, ça restait en général assez mystérieux tant qu'on avait pas identifié la cause au moins une fois.

      • [^] # Re: c'est probablement stupide

        Posté par . Évalué à 1.

        mhh effectivement, dans ce cas là c'est moche et mon intervention bien inutile.

        Soit dit en passant, freem, as tu trouvé ?

        • [^] # Re: c'est probablement stupide

          Posté par . Évalué à 1.

          Non, je n'ai pas eu le temps de chercher plus en détails encore, d'autres tâches m'ont occupé :/ ( m'en occupe la )

          Pour en revenir à ta question de comment je fais, en gros:

          Je commence par créer une réponse http correcte, c'est à dire, je génère une string contenant cet en-tête: "content-type: foo/bar\n\n" ( bien sûr, content-type ne vaut pas foo/bar. Dans mon cas, j'ai utilisé pour le moment "text/plain". )

          Puis je fais ma sauce, en créant des objets qui font le boulot, et qui ont une fonction "finale" qui renvoie une chaîne de caractères, que j'ajoute à mon en-tête, avant d'afficher le tout à la fin de mon script.
          J'ai imaginé que c'était la façon la plus propre de faire.

          Pour l'inexpérience dans l'écriture de CGI, je ne fais pas que la montrer: je l'admets largement.

          • [^] # Re: c'est probablement stupide

            Posté par . Évalué à 3.

            Au fait, en quel langage ton programme a-t-il été écrit ?

            Comme dit plus haut, il y a des chances pour que ton application déclenche une segfault dès le départ (relativement courant avec les CGI). Le mieux est de provoquer le dépôt d'un fichier « core » (fonctionnalité proposée par Unix et pas simplement par Apache) qui sert à cela. Regarde la directive CoreDumpDirectory pour voir où le générer.

            Une fois que tu l'as, tu l'ouvres avec « gdb  ».

            Tu retrouveras alors l'environnement du programme tel qu'il était au moment du déclenchement, avec notamment la pile des appels, et tu seras plus à même de localiser l'erreur.

  • # J'ai eu le même problème. Des solutions:

    Posté par . Évalué à 6.

    La solution la plus simple pour toi serait de reproduire le plus fidèlement possible la situation dans lequel ton programme est quand il plante. Remplace ton programme CGI par un script qui affiche le répertoire courant, les uid/gid du processus, les capabilities/contextes/autre machins (si ton serveur en utilise), toutes les variables environnement. tout les fd ouverts (ls /proc/$$/fd -l), puis reproduit ces conditions pour exécuter ton programme. Si ça marche, y a effectivement un problème. Mais souvent ça marche pas ;)

    Une autre solution qui m'a bien aidé, c'est de wrapper mon CGI pour qu'il soit lancé avec valgrind --log-file=/tmp/log. Pour les bugs que valgrind trouve en deux secondes, c'est idéal. Après tu l'oublie jusqu'au moment ou ton programme plante encore et ou tu te rend compte que tu à déjà la trace valgrind ;)

    Une autre solution que j'ai pas eu besoin d'essayer, c'est de wrapper avec gdbserver au lieu de valgrind: ton programme va attendre en tâche de fond jusqu'a que gdb s'y connecte. Pour avoir utilisé gdbserver par ailleurs, je préfère ne pas avoir à l'utiliser, mais bon…

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.