Journal Debug de code Python embarqué dans du code C++

Posté par  (site Web personnel) . Licence CC By‑SA.
Étiquettes :
35
30
sept.
2020

Demat' iNal,

On m'a posé tantôt une question assez intrigante au premier abord :

Dans une application native qui embarque un interpréteur Python, et que l'on est en train de déboguer avec gdb, comment faire pour débogguer le code Python associé ?

Pour donner un peu de contexte, on peut lire la doc Python et en extraire ce petit code C:

 #define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    wchar_t *program = Py_DecodeLocale(argv[0], NULL);
    if (program == NULL) {
        fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
        exit(1);
    }
    Py_SetProgramName(program);  /* optional but recommended */
    Py_Initialize();
    PyRun_SimpleString("from time import time,ctime\n"
                       "print('Today is', ctime(time()))\n");
    if (Py_FinalizeEx() < 0) {
        exit(120);
    }
    PyMem_RawFree(program);
    return 0;
}

Qui ne fait rien d'autre que démarrer un interpréteur Python. On peut compiler ce code d'un simple gcc python_from_c.c -o python_from_c `python-config --cflags --ldflags` -O0 -g et obtenir un petit binaire, qui, si on le lance (mais ni trop loin, ni trop fort), nous affiche la date du jour d'aujourd'hui.

Lançons ce programme dans gdb :

$ gdb python_from_c
(gdb) start
[...]
(gdb) n 5
Today is Wed Sep 30 19:12:48 2020
[...]

Arrivé là, on aimerait lancer pdb. Finalement, rien de plus facile !

(gdb) call PyRun_SimpleString("import pdb; pdb.set_trace()")
[...]
(pdb)

Hourra! On peut inspecter la pile Python, le tableau des globales etc!

(pdb) p ctime
<built-in function ctime>
(pdb) globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'time': <built-in function time>, 'ctime': <built-in function ctime>, 'pdb': <module 'pdb' from '/opt/rh/rh-python36/root/usr/lib64/python3.6/pdb.py'>, '__return__': None}
(pdb) exit()
[...]
(gdb)

Et voilà, on peut naviguer entre les deux mondes, avec une fluidité qui laisserait pantois de l'helium 4 à −270,98 °c

Je me suis longtemps demandé comment faire ça, et cette solution m'enjouaille comme un enfant, alors hop, je partage !

  • # C'est de la triche...

    Posté par  (site Web personnel) . Évalué à 9 (+9/-2).

    Demat' iNal,

    … c'est linuxfr ici, pas linuxbzh.

  • # Quelle simplicité!

    Posté par  . Évalué à 2 (+0/-0).

    J'avais pas idée que c'était aussi simple d'embarquer du Python en C/C++. Merci pour ce petit exemple bien parlant.

    • [^] # Re: Quelle simplicité!

      Posté par  . Évalué à 3 (+2/-0).

      Il y a embarquer et embarquer… Ici, l'embarcation ne va pas beaucoup plus loin qu'un simple system("python -c 'print(\"hello world!\))'");. Là où ça commence à devenir compliqué c'est lorsqu'il faut échanger des valeurs entre le monde python et le monde C avec des difficultés de gestion de la mémoire, conversion de types, contrôle de l'environnement (recherche des bibliothèques, contexte d'exécution), etc. De plus, l'auteur a choisi de s'interfacer avec la bibliothèque C de python qui (par définition) n'offre aucune facilité pour le C++. Là où une bibliothèque spécifiquement conçue pour le C++ (telle que Boost.Python ?) offrirait très certainement plus d'automatisme ou de facilités.
      Bref, je m'abstiendrais de tirer quelconques conclusions sur cet exemple si simple.

      PS: Le commentaire ci-dessous m'a intrigué: pourquoi interrompre le traitement si l'appel à Py_SetProgramName est optionnel ?

          if (program == NULL) {
              fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
              exit(1);
          }
          Py_SetProgramName(program);  /* optional but recommended */
      • [^] # Re: Quelle simplicité!

        Posté par  . Évalué à 2 (+1/-0).

        On pourrait même dire que l'existence même de ce journal prouve bien que ce n'est pas trivial d'embarquer un langage dans un autre :D Ceci dit, cela n'a rien de spécifique à python/c… Au même titre que l'écriture d'un interpréteur ad-hoc ou de faire de la génération de code, cela rend le débuggage plus complexe.

        • [^] # Re: Quelle simplicité!

          Posté par  . Évalué à 3 (+1/-0).

          J'ai vraiment rien à redire des intégrations intra jvm. J'ai embarqué du groovy dans du java et ça se passe vraiment très bien naturellement. Tu as une grande stack, mais ça c'est logiqu et pas plus gênant que ça.

          • [^] # Re: Quelle simplicité!

            Posté par  . Évalué à 2 (+1/-0). Dernière modification le 03/10/20 à 10:58.

            As-tu pu utiliser un débuggeur pas-à-pas ?

            Sinon tout à fait d'accord avec toi, j'ai faillit le mentionner mais amha c'est plus l'exception qui confirme la règle de ce qu'on peux trouver en mainstream… Notons qu'en java tu t'affranchis dès le départ une partie de la gestion de mémoire aussi. J'ai récemment développé un prototype en scala avec un DSL en groovy et j'ai trouvé celà très facile à faire et satisfaisant quant au résultat. Facile et modérément complexe :) Mais je n'ai jamais utilisé de debuggeur java, c'est aspect là m'échappe.

Envoyer un commentaire

Suivre le flux des commentaires

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