/* Chrysalide - Outil d'analyse de fichiers binaires * core.c - plugin permettant des extensions en Python * * Copyright (C) 2018-2019 Cyrille Bagard * * This file is part of Chrysalide. * * Chrysalide is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Chrysalide is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Chrysalide. If not, see . */ #undef NO_IMPORT_PYGOBJECT #include #define NO_IMPORT_PYGOBJECT #include "core.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "access.h" #include "constants.h" #include "helpers.h" #include "star.h" #include "strenum.h" #include "struct.h" #include "analysis/module.h" #include "arch/module.h" #include "common/module.h" #include "core/module.h" #include "glibext/module.h" /* #include "debug/module.h" */ #include "format/module.h" /* #ifdef INCLUDE_GTK_SUPPORT */ /* # include "gtkext/module.h" */ /* # include "gui/module.h" */ /* #endif */ /* #include "mangling/module.h" */ #include "plugins/module.h" #include "plugins/plugin.h" DEFINE_CHRYSALIDE_CONTAINER_PLUGIN("PyChrysalide", "Chrysalide bindings to Python", PACKAGE_VERSION, CHRYSALIDE_WEBSITE("api/python/pychrysalide"), NO_REQ, AL(PGA_PLUGIN_INIT, PGA_PLUGIN_EXIT, PGA_NATIVE_PLUGINS_LOADED, PGA_TYPE_BUILDING)); /* Note la nature du chargement */ static bool _standalone = true; /* Réceptacle pour le chargement forcé */ static PyObject *_chrysalide_module = NULL; /* Fournit la révision du programme global. */ static PyObject *py_chrysalide_revision(PyObject *, PyObject *); /* Fournit la version du programme global. */ static PyObject *py_chrysalide_version(PyObject *, PyObject *); /* Fournit la version du greffon pour Python. */ static PyObject *py_chrysalide_mod_version(PyObject *, PyObject *); /* Détermine si l'interpréteur lancé est celui pris en compte. */ static bool is_current_abi_suitable(void); /* Assure une pleine initialisation des objets de Python-GI. */ static bool install_metaclass_for_python_gobjects(void); /* Définit la version attendue de GTK à charger dans Python. */ #ifdef INCLUDE_GTK_SUPPORT static bool set_version_for_gtk_namespace(const char *); #endif /* Point de sortie pour l'initialisation de Python. */ static void PyExit_pychrysalide(void); /* Complète les chemins de recherches de Python. */ static void extend_python_path(const char *); /* Charge autant de greffons composés en Python que possible. */ static void load_python_plugins(GPluginModule *); /* Efface un type Python pour greffon de la mémoire. */ static void free_native_plugin_type(PyTypeObject *); /****************************************************************************** * * * Paramètres : self = NULL car méthode statique. * * args = non utilisé ici. * * * * Description : Fournit la révision du programme global. * * * * Retour : Numéro de révision. * * * * Remarques : - * * * ******************************************************************************/ static PyObject *py_chrysalide_revision(PyObject *self, PyObject *args) { PyObject *result; /* Valeur à retourner */ #define PY_CHRYSALIDE_REVISION_METHOD PYTHON_METHOD_DEF \ ( \ revision, "/", \ METH_NOARGS, py_chrysalide, \ "Provide the revision number of Chrysalide.\n" \ "\n" \ "The returned value is provided as a string, for instance: 'r1665'." \ ) result = PyUnicode_FromString("r" XSTR(REVISION)); return result; } /****************************************************************************** * * * Paramètres : self = NULL car méthode statique. * * args = non utilisé ici. * * * * Description : Fournit la version du programme global. * * * * Retour : Numéro de version. * * * * Remarques : - * * * ******************************************************************************/ static PyObject *py_chrysalide_version(PyObject *self, PyObject *args) { PyObject *result; /* Valeur à retourner */ int major; /* Numéro de version majeur */ int minor; /* Numéro de version mineur */ int revision; /* Numéro de révision */ char version[16]; /* Conservation temporaire */ #define PY_CHRYSALIDE_VERSION_METHOD PYTHON_METHOD_DEF \ ( \ version, "/", \ METH_NOARGS, py_chrysalide, \ "Provide the version number of Chrysalide.\n" \ "\n" \ "The returned value is provided as a string, for instance: '1.6.65'." \ ) major = REVISION / 1000; minor = (REVISION - (major * 1000)) / 100; revision = REVISION % 100; snprintf(version, sizeof(version), "%d.%d.%d", major, minor, revision); result = PyUnicode_FromString(version); return result; } /****************************************************************************** * * * Paramètres : self = NULL car méthode statique. * * args = non utilisé ici. * * * * Description : Fournit la version du greffon pour Python. * * * * Retour : Numéro de version. * * * * Remarques : - * * * ******************************************************************************/ static PyObject *py_chrysalide_mod_version(PyObject *self, PyObject *args) { PyObject *result; /* Valeur à retourner */ char version[16]; /* Conservation temporaire */ #define PY_CHRYSALIDE_MOD_VERSION_METHOD PYTHON_METHOD_DEF \ ( \ mod_version, "/", \ METH_NOARGS, py_chrysalide, \ "Provide the version number of Chrysalide module for Python.\n" \ "\n" \ "The returned value is provided as a string, for instance: '0.1.0'." \ ) snprintf(version, sizeof(version), "%s", _chrysalide_plugin.version); result = PyUnicode_FromString(version); return result; } /****************************************************************************** * * * Paramètres : - * * * * Description : Détermine si l'interpréteur lancé est celui pris en compte. * * * * Retour : true si l'exécution peut se poursuivre, false sinon. * * * * Remarques : - * * * ******************************************************************************/ static bool is_current_abi_suitable(void) { bool result; int fds[2]; int ret; char cmds[128]; char content[64]; ssize_t got; #define GRAB_ABI_FLAGS_IN_PYTHON \ "import sys" "\n" \ "import os" "\n" \ "data = bytes(sys.abiflags, 'UTF-8') + b'\\0'" "\n" \ "os.write(%d, data)" "\n" result = false; ret = pipe(fds); if (ret == -1) { perror("pipe()"); return false; } snprintf(cmds, sizeof(cmds), GRAB_ABI_FLAGS_IN_PYTHON, fds[1]); ret = PyRun_SimpleString(cmds); if (ret != 0) goto icas_exit; got = read(fds[0], content, sizeof(content)); if (got < 0) { perror("read()"); goto icas_exit; } content[got] = '\0'; result = (strcmp(content, LIBPYTHON_ABI_FLAGS) == 0); icas_exit: if (!result) PyErr_SetString(PyExc_SystemError, "the ABI flags of the current interpreter do not match " \ "the ones of the Python library used during the module compilation."); return result; } /****************************************************************************** * * * Paramètres : - * * * * Description : Assure une pleine initialisation des objets de Python-GI. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool install_metaclass_for_python_gobjects(void) { bool result; /* Bilan à retourner */ PyObject *gi_types_mod; /* Module Python-GObject */ /** * Les extensions Python sont chargées à partir de la fonction load_python_plugins(), * qui fait appel à create_python_plugin(). Une instance y est construite via un * appel à PyObject_CallFunction() avec la classe spécifiée par l'alias AutoLoad * dans le fichier __init__.py présent dans chaque module d'extension. * * Le constructeur py_plugin_module_new() renvoie in fine à la fonction générique * python_abstract_constructor_with_dynamic_gtype(), laquelle conduit à la fonction * pygobject_register_class() définie dans /gi/pygobject-object.c. * Le code de cette dernière comprend notamment la portion suivante : * * [...] * Py_SET_TYPE(type, PyGObject_MetaType); * [...] * if (PyType_Ready(type) < 0) { * g_warning ("couldn't make the type `%s' ready", type->tp_name); * return; * } * [...] * * La fonction PyType_Ready() est définie dans /Objects/typeobject.c * et commence par : * * int PyType_Ready(PyTypeObject *type) * { * if (type->tp_flags & Py_TPFLAGS_READY) { * assert(_PyType_CheckConsistency(type)); * return 0; * } * [...] * } * * La vérification de cohérencce commence par analyser le type et son propre * type : * * - cf. _PyType_CheckConsistency() dans /Objects/typeobject.c : * * int _PyType_CheckConsistency(PyTypeObject *type) * { * [...] * CHECK(!_PyObject_IsFreed((PyObject *)type)); * [...] * } * * - cf. _PyObject_IsFreed() dans /Objects/object.c : * * int _PyObject_IsFreed(PyObject *op) * { * if (_PyMem_IsPtrFreed(op) || _PyMem_IsPtrFreed(Py_TYPE(op))) { * return 1; * } * * La fonction _PyMem_IsPtrFreed() recherche entre autres la valeur NULL. * * Or le type du type est écrasé dans la fonction pygobject_register_class() * avec la valeur de la variable PyGObject_MetaType. Cette variable n'est * définie qu'à un seul endroit, dans /gi/gimodule.c : * * static PyObject * * pyg__install_metaclass(PyObject *dummy, PyTypeObject *metaclass) * { * Py_INCREF(metaclass); * PyGObject_MetaType = metaclass; * Py_INCREF(metaclass); * * Py_SET_TYPE(&PyGObject_Type, metaclass); * * Py_INCREF(Py_None); * return Py_None; * } * * Afin de valider la vérification de _PyType_CheckConsistency() pour les * modules externes qui entraînent un enregistrement tout en portant le drapeau * Py_TPFLAGS_READY (typiquement ceux du répertoire "plugins/python/", il faut * initialiser au besoin la variable PyGObject_MetaType. * * Une ligne suffit donc à enregistrer le type intermédiaire : * * from _gi import types * * On simule ici une déclaration similaire si nécessaire */ result = false; if (PyType_CheckExact(&PyGObject_Type)) { gi_types_mod = PyImport_ImportModule("gi.types"); result = (PyErr_Occurred() == NULL); if (result) result = (PyType_CheckExact(&PyGObject_Type) == 0); Py_XDECREF(gi_types_mod); } return result; } /****************************************************************************** * * * Paramètres : version = idenfiant de la version de GTK à stipuler. * * * * Description : Définit la version attendue de GTK à charger dans Python. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ #ifdef INCLUDE_GTK_SUPPORT static bool set_version_for_gtk_namespace(const char *version) { bool result; /* Bilan à retourner */ PyObject *gi_mod; /* Module Python-GObject */ PyObject *args; /* Arguments à fournir */ result = false; /** * On cherche ici à éviter le message suivant si on charge 'gi.repository.Gtk' directement : * * * PyGIWarning: Gtk was imported without specifying a version first. \ * Use gi.require_version('Gtk', '3.0') before import to ensure that the right version gets loaded. * */ gi_mod = PyImport_ImportModule("gi"); if (gi_mod != NULL) { args = Py_BuildValue("ss", "Gtk", version); run_python_method(gi_mod, "require_version", args); result = (PyErr_Occurred() == NULL); Py_DECREF(args); Py_DECREF(gi_mod); } return result; } #endif /****************************************************************************** * * * Paramètres : - * * * * Description : Point de sortie pour l'initialisation de Python. * * * * Retour : ? * * * * Remarques : - * * * ******************************************************************************/ static void PyExit_pychrysalide(void) { assert(_standalone); /* extern void set_current_project(void *project); set_current_project(NULL); */ #ifdef TRACK_GOBJECT_LEAKS remember_gtypes_for_leaks(); #endif exit_all_plugins(); //unload_all_core_components(true); #ifdef TRACK_GOBJECT_LEAKS dump_remaining_gtypes(); #endif } /****************************************************************************** * * * Paramètres : - * * * * Description : Point d'entrée pour l'initialisation de Python. * * * * Retour : ? * * * * Remarques : - * * * ******************************************************************************/ #define PYCHRYSALIDE_DOC \ "PyChrysalide is a module containing Chrysalide's features and designed for Python users.\n" \ "\n" \ "The whole API is defined in a single library named 'pychrysalide.so' and can be used in two ways:\n" \ "* either from the Chrysalide's GUI, by registering hooks or GLib signals;\n" \ "* or from a shell command line, by setting PYTHONPATH to point to the directory containing the library.\n" \ "\n" \ "In both cases, this is a good start point to have a look at already existing plugins to quickly learn " \ "how the API works.\n" \ "\n" \ "These plugins are located in the 'plugins/python' directory.\n" \ "\n" \ "The *pychrysalide* module imports the GLib module (version 2.0) from the GI repository at startup." PyMODINIT_FUNC PyInit_pychrysalide(void) { PyObject *result; /* Module Python à retourner */ bool status; /* Bilan des inclusions */ int ret; /* Bilan de préparatifs */ #ifdef PYTHON_PACKAGE Dl_info info; /* Informations dynamiques */ #endif GPluginModule *self; /* Représentation interne */ PluginStatusFlags self_flags; /* Fanions à mettre à jour */ static PyMethodDef py_chrysalide_methods[] = { PY_CHRYSALIDE_REVISION_METHOD, PY_CHRYSALIDE_VERSION_METHOD, PY_CHRYSALIDE_MOD_VERSION_METHOD, { NULL } }; static PyModuleDef py_chrysalide_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "pychrysalide", .m_doc = PYCHRYSALIDE_DOC, .m_size = -1, .m_methods = py_chrysalide_methods }; /** * Vérification préalable : dans le cas où on est embarqué directement dans * un interpréteur Python, le module se charge et termine par charger à leur * tour les différentes extensions trouvées, via load_remaning_plugins() puis * chrysalide_plugin_on_native_loaded(). * * Lesquelles vont très probablement charger le module pychrysalide. * * Comme le chargement de ce dernier n'est alors pas encore terminé, * Python va relancer cette procédure, et register_access_to_python_module() * va détecter un doublon. */ result = get_access_to_python_module(py_chrysalide_module.m_name); if (result != NULL) { Py_INCREF(result); return result; } if (!is_current_abi_suitable()) goto exit; if (pygobject_init(-1, -1, -1) == NULL) { PyErr_SetString(PyExc_SystemError, "unable to init GObject in Python."); goto exit; } if (!install_metaclass_for_python_gobjects()) goto exit; /** * Le chargement forcé de l'espace GLib pour Python permet d'éviter un écueil, * à savoir des types convertis de façon incomplète. Par exemple, pour une * structure GChecksum, le type à l'exécution est : * * - sans module GLib : [, ] * * - avec module GLib : [, , , ] * * Par ailleurs, il est à noter que le message suivant n'apparaît qu'avec * la version debug de Python3 (version de python3-gi : 3.42.2-3) : * * :673: ImportWarning: DynamicImporter.exec_module() not found; falling back to load_module() * * Code de reproduction dans un interpréteur classique : * * import gi * gi.require_version('GLib', '2.0') * from gi.repository import GLib * */ if (!import_namespace_from_gi_repository("GLib", "2.0")) goto exit; #if 0 #ifdef INCLUDE_GTK_SUPPORT if (!set_version_for_gtk_namespace("3.0")) goto exit; #endif #endif if (!load_core_components(ACC_GLOBAL_VARS)) { PyErr_SetString(PyExc_SystemError, "unable to load all basic components."); goto exit; } /* Mise en place des fonctionnalités offertes */ result = PyModule_Create(&py_chrysalide_module); register_access_to_python_module(py_chrysalide_module.m_name, result); status = true; if (status) status = add_features_module(result); if (status) status = add_analysis_module(result); if (status) status = add_arch_module(result); if (status) status = add_common_module(result); if (status) status = add_glibext_module(result); if (status) status = add_core_module(result); /* if (status) status = add_debug_module(result); */ if (status) status = add_format_module(result); /* #ifdef INCLUDE_GTK_SUPPORT if (status) status = add_gtkext_module(result); if (status) status = add_gui_module(result); #endif if (status) status = add_mangling_module(result); */ if (status) status = add_plugins_module(result); /* if (status) status = ensure_python_string_enum_is_registered(); */ if (status) status = ensure_python_py_struct_is_registered(); if (status) status = define_data_types_constants(result); if (status) status = populate_analysis_module(); if (status) status = populate_arch_module(); if (status) status = populate_glibext_module(); if (status) status = populate_common_module(); if (status) status = populate_core_module(); /* if (status) status = populate_debug_module(); */ if (status) status = populate_format_module(); /* #ifdef INCLUDE_GTK_SUPPORT if (status) status = populate_gtkext_module(); if (status) status = populate_gui_module(); #endif if (status) status = populate_mangling_module(); */ if (status) status = populate_plugins_module(); if (!status) { PyErr_SetString(PyExc_SystemError, "failed to load all PyChrysalide components."); Py_DECREF(result); result = NULL; goto exit; } if (_standalone) { ret = Py_AtExit(PyExit_pychrysalide); if (ret == -1) { PyErr_SetString(PyExc_SystemError, "failed to register a cleanup function."); Py_DECREF(result); result = NULL; goto exit; } /** * Comme les sources locales sont prioritaires, le fichier "core/global.h" * du greffon masque la fonction suivante, issue du corps principal du * programme. * * On la déclare donc à la main. */ /* extern void set_batch_mode(void); set_batch_mode(); */ /** * Si cette extension pour Python est chargée depuis un dépôt Python, * elle ne se trouve pas dans le répertoire classique des extensions et * n'est donc pas chargée et enregistrée comme attendu. * * Cet enregistrement est donc forcé ici. */ #ifdef PYTHON_PACKAGE ret = dladdr(__FUNCTION__, &info); if (ret == 0) { LOG_ERROR_DL_N("dladdr"); Py_DECREF(result); result = NULL; goto exit; } self = g_plugin_module_new(info.dli_fname); assert(self != NULL); register_plugin(self); #endif init_all_plugins(false); lock_plugin_list_for_reading(); self = get_plugin_by_name("PyChrysalide", NULL); assert(self != NULL); self_flags = g_plugin_module_get_flags(self); self_flags &= ~(PSF_FAILURE | PSF_LOADED); self_flags |= (status ? PSF_LOADED : PSF_FAILURE); g_plugin_module_override_flags(self, self_flags); unlock_plugin_list_for_reading(); load_remaning_plugins(); /** * On laisse fuir ici la référence sur self afin d'avoir * l'assurance que le greffon se déchargera toujours en dernier. * * La fuite mémoire est au final évitée dans PyExit_pychrysalide(). */ } exit: if (result == NULL && !_standalone) log_pychrysalide_exception("Loading failed"); return result; } /****************************************************************************** * * * Paramètres : path = chemin supplémentaire pour l'espace de recherche. * * * * Description : Complète les chemins de recherches de Python. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void extend_python_path(const char *path) { PyObject *list; /* Liste de chemins à compléter*/ PyObject *new; /* Nouveau chemin à intégrer */ list = PySys_GetObject("path"); assert(list != NULL); new = PyUnicode_FromString(path); assert(new != NULL); PyList_Append(list, new); Py_DECREF(new); add_to_env_var("PYTHONPATH", path, ":"); } /****************************************************************************** * * * Paramètres : plugin = instance représentant le greffon Python d'origine. * * * * Description : Charge autant de greffons composés en Python que possible. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void load_python_plugins(GPluginModule *plugin) { #ifdef DISCARD_LOCAL char *edir; /* Répertoire de base effectif */ #endif DIR *dir; /* Répertoire à parcourir */ char *paths; /* Emplacements de greffons */ char *save; /* Sauvegarde pour ré-entrance */ char *path; /* Chemin à fouiller */ struct dirent *entry; /* Elément trouvé */ char *modname; /* Nom du module pour Python */ char *filename; /* Chemin d'accès reconstruit */ GPluginModule *pyplugin; /* Lien vers un grffon Python */ bool status; /* Bilan d'une opération */ //GGenConfig *config; /* Configuration à charger */ /* Définition des zones d'influence */ #ifndef DISCARD_LOCAL extend_python_path(PACKAGE_SOURCE_DIR G_DIR_SEPARATOR_S "plugins" G_DIR_SEPARATOR_S "python"); #else edir = get_effective_directory(PLUGINS_DATA_DIR G_DIR_SEPARATOR_S "python"); dir = opendir(edir); free(edir); if (dir != NULL) { closedir(dir); edir = get_effective_directory(PLUGINS_DATA_DIR G_DIR_SEPARATOR_S "python"); extend_python_path(edir); free(edir); } #endif g_plugin_module_log_variadic_message(plugin, LMT_INFO, _("PYTHONPATH environment variable set to '%s'"), getenv("PYTHONPATH")); /* Chargements des extensions Python */ paths = get_env_var("PYTHONPATH"); save = NULL; /* gcc... */ for (path = strtok_r(paths, ":", &save); path != NULL; path = strtok_r(NULL, ":", &save)) { dir = opendir(path); if (dir == NULL) { perror("opendir"); continue; } g_plugin_module_log_variadic_message(plugin, LMT_INFO, _("Looking for Python plugins in '%s'..."), path); while (1) { errno = 0; entry = readdir(dir); if (entry == NULL) { if (errno != 0) perror("readdir"); break; } if (entry->d_type != DT_DIR) continue; if (entry->d_name[0] == '.') continue; modname = strdup(entry->d_name); modname = stradd(modname, "."); modname = stradd(modname, "__init__"); filename = strdup(path); filename = stradd(filename, G_DIR_SEPARATOR_S); filename = stradd(filename, entry->d_name); pyplugin = create_python_plugin(modname, filename); if (pyplugin == NULL) { g_plugin_module_log_variadic_message(plugin, LMT_ERROR, _("No suitable Python plugin found in '%s'"), filename); goto done_with_plugin; } //g_plugin_module_create_config(pyplugin); status = g_plugin_module_manage(pyplugin, PGA_PLUGIN_LOADED); if (!status) { g_plugin_module_log_variadic_message(plugin, LMT_ERROR, _("Plugin '%s' failed to complete loading..."), filename); g_object_unref(G_OBJECT(pyplugin)); goto done_with_plugin; } /* config = g_plugin_module_get_config(pyplugin); g_generic_config_read(config); g_object_unref(G_OBJECT(config)); */ g_plugin_module_log_variadic_message(plugin, LMT_PROCESS, _("Loaded the Python plugin found in the '%s' directory"), filename); _register_plugin(pyplugin); done_with_plugin: free(filename); free(modname); } closedir(dir); } free(paths); } /****************************************************************************** * * * Paramètres : plugin = greffon à manipuler. * * * * Description : Prend acte du chargement du greffon. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ G_MODULE_EXPORT bool chrysalide_plugin_init(GPluginModule *plugin) { bool result; /* Bilan à retourner */ PyGILState_STATE gstate; /* Sauvegarde d'environnement */ int ret; /* Bilan de préparatifs */ _standalone = false; /* Chargement du module pour Python */ ret = PyImport_AppendInittab("pychrysalide", &PyInit_pychrysalide); if (ret == -1) { log_plugin_simple_message(LMT_ERROR, _("Can not extend the existing table of Python built-in modules.")); result = false; goto cpi_done; } Py_Initialize(); gstate = PyGILState_Ensure(); _chrysalide_module = PyImport_ImportModule("pychrysalide"); /** * Pour mémoire, une situation concrête conduisant à un échec : * le paquet python3-gi-dbg n'est pas installé alors que le * programme est compilé en mode débogage. * * Dans ce cas, pygobject_init(-1, -1, -1) échoue, et Py_Initialize() * le laisse rien filtrer... * * En mode autonome, le shell Python remonte bien l'erreur par contre. */ result = (_chrysalide_module != NULL); PyGILState_Release(gstate); cpi_done: return result; } /****************************************************************************** * * * Paramètres : plugin = greffon à manipuler. * * * * Description : Prend acte du déchargement du greffon. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ G_MODULE_EXPORT void chrysalide_plugin_exit(GPluginModule *plugin) { PyGILState_STATE gstate; /* Sauvegarde d'environnement */ gstate = PyGILState_Ensure(); clear_all_accesses_to_python_modules(); Py_XDECREF(_chrysalide_module); PyGILState_Release(gstate); } /****************************************************************************** * * * Paramètres : type = informations à libérer de la mémoire. * * * * Description : Efface un type Python pour greffon de la mémoire. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void free_native_plugin_type(PyTypeObject *type) { free((char *)type->tp_name); free((char *)type->tp_doc); free(type); } /****************************************************************************** * * * Paramètres : plugin = greffon à manipuler. * * action = type d'action attendue. * * * * Description : Accompagne la fin du chargement des modules natifs. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ G_MODULE_EXPORT void chrysalide_plugin_on_plugins_loaded(GPluginModule *plugin, PluginAction action) { PyGILState_STATE gstate; /* Sauvegarde d'environnement */ size_t count; /* Quantité de greffons chargés*/ PyObject *module; /* Module à recompléter */ PyObject *dict; /* Dictionnaire du module */ GPluginModule **list; /* Ensemble de ces greffons */ size_t i; /* Boucle de parcours */ char *name; /* Désignation complète */ char *doc; /* Description adaptée */ int ret; /* Bilan d'un appel */ PyTypeObject *type; /* Nouveau type dynamique */ gstate = PyGILState_Ensure(); if (action == PGA_NATIVE_PLUGINS_LOADED) { /* Intégration des greffons natifs en Python */ if (ensure_python_plugin_module_is_registered()) { module = get_access_to_python_module("pychrysalide.plugins"); assert(module != NULL); dict = PyModule_GetDict(module); list = get_all_plugins(&count); for (i = 0; i < count; i++) { ret = asprintf(&name, "pychrysalide.plugins.%s", G_OBJECT_TYPE_NAME(list[i]) + 1); if (ret == -1) { LOG_ERROR_N("asprintf"); continue; } ret = asprintf(&doc, "Place holder for the native plugin %s documentation", G_OBJECT_TYPE_NAME(list[i]) + 1); if (ret == -1) { LOG_ERROR_N("asprintf"); free(name); continue; } type = calloc(1, sizeof(PyTypeObject)); type->tp_name = name; type->tp_doc = doc; type->tp_flags = Py_TPFLAGS_DEFAULT; type->tp_new = no_python_constructor_allowed; if (register_class_for_pygobject(dict, G_OBJECT_TYPE(list[i]), type)) g_object_set_data_full(G_OBJECT(list[i]), "python_type", type, (GDestroyNotify)free_native_plugin_type); else free_native_plugin_type(type); } if (list != NULL) free(list); } /* Chargement des extensions purement Python */ load_python_plugins(plugin); } PyGILState_Release(gstate); } /****************************************************************************** * * * Paramètres : plugin = greffon à manipuler. * * action = type d'action attendue. * * type = type d'objet à mettre en place. * * * * Description : Crée une instance à partir d'un type dynamique externe. * * * * Retour : Instance d'objet gérée par l'extension ou NULL. * * * * Remarques : - * * * ******************************************************************************/ G_MODULE_EXPORT gpointer chrysalide_plugin_build_type_instance(GPluginModule *plugin, PluginAction action, GType type) { gpointer result; /* Instance à retourner */ PyGILState_STATE gstate; /* Sauvegarde d'environnement */ PyTypeObject *pytype; /* Classe Python concernée */ PyObject *instance; /* Initialisation forcée */ result = NULL; gstate = PyGILState_Ensure(); pytype = pygobject_lookup_class(type); if (pytype != NULL) { instance = PyObject_CallObject((PyObject *)pytype, NULL); assert(instance != NULL); result = pygobject_get(instance); } PyGILState_Release(gstate); return result; } /****************************************************************************** * * * Paramètres : namespace = module particulier à charger à partir de gi. * * version = idenfiant de la version à stipuler. * * * * Description : Charge un module GI dans Python avec une version attendue. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool import_namespace_from_gi_repository(const char *namespace, const char *version) { bool result; /* Bilan à retourner */ PyObject *module; /* Module Python-GObject */ PyObject *args; /* Arguments à fournir */ int ret; /* Bilan d'une mise en place */ result = false; /* Sélection d'une version */ module = PyImport_ImportModule("gi"); if (module != NULL) { args = Py_BuildValue("ss", namespace, version); run_python_method(module, "require_version", args); result = (PyErr_Occurred() == NULL); Py_DECREF(args); Py_DECREF(module); } /* Importation du module visé */ if (result) { args = PyTuple_New(1); ret = PyTuple_SetItem(args, 0, PyUnicode_FromString(namespace)); if (ret != 0) { result = false; goto args_error; } module = PyImport_ImportModuleEx("gi.repository", NULL, NULL, args); result = (module != NULL); Py_XDECREF(module); args_error: Py_DECREF(args); } return result; } /****************************************************************************** * * * Paramètres : prefix = message d'introduction à faire apparaître à l'écran.* * * * Description : Présente dans le journal une exception survenue. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void log_pychrysalide_exception(const char *prefix, ...) { va_list ap; /* Compléments argumentaires */ char *msg; /* Message complet à imprimer */ PyObject *err_type; /* Type d'erreur Python */ PyObject *err_value; /* Instance Python d'erreur */ PyObject *err_traceback; /* Trace Python associée */ PyObject *err_string; /* Description Python d'erreur */ const char *err_msg; /* Représentation humaine */ assert(PyGILState_Check() == 1); if (PyErr_Occurred()) { /* Base de la communication */ va_start(ap, prefix); vasprintf(&msg, prefix, ap); va_end(ap); /* Détails complémentaires */ PyErr_Fetch(&err_type, &err_value, &err_traceback); PyErr_NormalizeException(&err_type, &err_value, &err_traceback); if (err_traceback == NULL) { err_traceback = Py_None; Py_INCREF(err_traceback); } PyException_SetTraceback(err_value, err_traceback); if (err_value == NULL) msg = stradd(msg, _(": no extra information is provided...")); else { err_string = PyObject_Str(err_value); err_msg = PyUnicode_AsUTF8(err_string); msg = stradd(msg, ": "); msg = stradd(msg, err_msg); Py_DECREF(err_string); } /** * Bien que la documentation précise que la fonction PyErr_Fetch() * transfère la propritété des éléments retournés, la pratique * montre que le programme plante à la terminaison en cas d'exception. * * C'est par exemple le cas quand un greffon Python ne peut se lancer * correctement ; l'exception est alors levée à partir de la fonction * create_python_plugin() et le plantage intervient en sortie d'exécution, * au moment de la libération de l'extension Python : * * ==14939== Jump to the invalid address stated on the next line * ==14939== at 0x1A8FCBC9: ??? * ==14939== by 0x53DCDB2: g_object_unref (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.5800.3) * ==14939== by 0x610F834: on_plugin_ref_toggle (pglist.c:370) * ==14939== by 0x610F31A: exit_all_plugins (pglist.c:153) * ==14939== by 0x10AD19: main (main.c:440) * ==14939== Address 0x1a8fcbc9 is not stack'd, malloc'd or (recently) free'd * * Curieusement, un appel à PyErr_PrintEx(1) corrige l'effet, alors qu'un * appel à PyErr_PrintEx(0) ne change rien. * * La seule différence de l'instruction set_sys_last_vars réside en quelques * lignes dans le code de l'interpréteur Python : * * if (set_sys_last_vars) { * _PySys_SetObjectId(&PyId_last_type, exception); * _PySys_SetObjectId(&PyId_last_value, v); * _PySys_SetObjectId(&PyId_last_traceback, tb); * } * * L'explication n'est pas encore déterminé : bogue dans Chrysalide ou dans Python ? * L'ajout des éléments dans le dictionnaire du module sys ajoute une référence * à ces éléments. * * On reproduit ici le comportement du code correcteur avec PySys_SetObject(). */ PySys_SetObject("last_type", err_type); PySys_SetObject("last_value", err_value); PySys_SetObject("last_traceback", err_traceback); Py_XDECREF(err_traceback); Py_XDECREF(err_value); Py_XDECREF(err_type); log_plugin_simple_message(LMT_ERROR, msg); free(msg); } }