/* Chrysalide - Outil d'analyse de fichiers binaires * core.c - plugin permettant des extensions en Python * * Copyright (C) 2018-2025 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 . */ #include "core.h" #include "core-int.h" #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 "access.h" #include "bindings.h" /* Note la nature du chargement */ static bool _standalone = true; /* ---------------------- COMPOSITION DE NOUVEAU GREFFON NATIF ---------------------- */ /* Initialise la classe des greffons de support Python. */ static void g_pychrysalide_plugin_class_init(GPyChrysalidePluginClass *); /* Procède à l'initialisation de l'interface de gestion. */ static void g_pychrysalide_plugin_plugin_manager_interface_init(GPluginManagerInterface *); /* Initialise une instance de greffon de support Python. */ static void g_pychrysalide_plugin_init(GPyChrysalidePlugin *); /* Supprime toutes les références externes. */ static void g_pychrysalide_plugin_dispose(GPyChrysalidePlugin *); /* Procède à la libération totale de la mémoire. */ static void g_pychrysalide_plugin_finalize(GPyChrysalidePlugin *); /* --------------------- IMPLEMENTATION DES FONCTIONS DE CLASSE --------------------- */ /* Prend acte de l'activation du greffon. */ static bool g_pychrysalide_plugin_enable(GPyChrysalidePlugin *); /* Prend acte de la désactivation du greffon. */ static bool g_pychrysalide_plugin_disable(GPyChrysalidePlugin *); /* -------------------- INTERVENTION DANS LA GESTION DE GREFFONS -------------------- */ /* Complète les chemins de recherches de Python. */ static void extend_python_path(const char *); /* Crée un greffon à partir de code Python. */ static GPluginModule *create_python_plugin(const char *, const char *); /* Charge autant de greffons composés en Python que possible. */ static void load_python_plugins(GPluginModule *); /* Prend acte du chargement de l'ensemble des greffons natifs. */ static void g_pychrysalide_plugin_handle_native_plugins_loaded_event(GPyChrysalidePlugin *); /* ---------------------------------------------------------------------------------- */ /* COMPOSITION DE NOUVEAU GREFFON NATIF */ /* ---------------------------------------------------------------------------------- */ /* Indique le type défini pour un greffon de liaison Python */ G_DEFINE_TYPE_WITH_CODE(GPyChrysalidePlugin, g_pychrysalide_plugin, G_TYPE_NATIVE_PLUGIN, G_IMPLEMENT_INTERFACE(G_TYPE_PLUGIN_MANAGER, g_pychrysalide_plugin_plugin_manager_interface_init)); NATIVE_PLUGIN_ENTRYPOINT(g_pychrysalide_plugin_new); /****************************************************************************** * * * Paramètres : class = classe à initialiser. * * * * Description : Initialise la classe des greffons de support Python. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_pychrysalide_plugin_class_init(GPyChrysalidePluginClass *class) { GObjectClass *object; /* Autre version de la classe */ GPluginModuleClass *plugin; /* Version parente de la classe*/ object = G_OBJECT_CLASS(class); object->dispose = (GObjectFinalizeFunc/* ! */)g_pychrysalide_plugin_dispose; object->finalize = (GObjectFinalizeFunc)g_pychrysalide_plugin_finalize; plugin = G_PLUGIN_MODULE_CLASS(class); plugin->enable = (pg_management_fc)g_pychrysalide_plugin_enable; plugin->disable = (pg_management_fc)g_pychrysalide_plugin_disable; } /****************************************************************************** * * * Paramètres : iface = interface GLib à initialiser. * * * * Description : Procède à l'initialisation de l'interface de gestion. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_pychrysalide_plugin_plugin_manager_interface_init(GPluginManagerInterface *iface) { iface->handle_native = (handle_native_plugins_cb)g_pychrysalide_plugin_handle_native_plugins_loaded_event; } /****************************************************************************** * * * Paramètres : plugin = instance à initialiser. * * * * Description : Initialise une instance de greffon de support Python. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_pychrysalide_plugin_init(GPyChrysalidePlugin *plugin) { STORE_PLUGIN_ABI(plugin); plugin->py_module = NULL; } /****************************************************************************** * * * Paramètres : plugin = instance d'objet GLib à traiter. * * * * Description : Supprime toutes les références externes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_pychrysalide_plugin_dispose(GPyChrysalidePlugin *plugin) { G_OBJECT_CLASS(g_pychrysalide_plugin_parent_class)->dispose(G_OBJECT(plugin)); } /****************************************************************************** * * * Paramètres : plugin = instance d'objet GLib à traiter. * * * * Description : Procède à la libération totale de la mémoire. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_pychrysalide_plugin_finalize(GPyChrysalidePlugin *plugin) { G_OBJECT_CLASS(g_pychrysalide_plugin_parent_class)->finalize(G_OBJECT(plugin)); } /****************************************************************************** * * * Paramètres : filename = nom du fichier à charger. * * * * Description : Crée un module pour un greffon de support Python. * * * * Retour : Adresse de la structure mise en place. * * * * Remarques : Le transfert de propriétée du module est total. * * * ******************************************************************************/ GPluginModule *g_pychrysalide_plugin_new(GModule *module) { GPyChrysalidePlugin *result; /* Structure à retourner */ result = g_object_new(G_TYPE_PYCHRYSALIDE_PLUGIN, NULL); if (!g_pychrysalide_plugin_create(result, module)) g_clear_object(&result); return G_PLUGIN_MODULE(result); } /****************************************************************************** * * * Paramètres : plugin = instance à initialiser pleinement. * * module = module système correspondant. * * * * Description : Met en place un module pour un greffon de support Python. * * * * Retour : Bilan de l'opération. * * * * Remarques : Le transfert de propriétée du module est total. * * * ******************************************************************************/ bool g_pychrysalide_plugin_create(GPyChrysalidePlugin *plugin, GModule *module) { bool result; /* Bilan à retourner */ result = g_native_plugin_create(G_NATIVE_PLUGIN(plugin), "PyChrysalide", "Chrysalide bindings to Python", PACKAGE_VERSION, CHRYSALIDE_WEBSITE("api/python/pychrysalide"), NO_REQ, module); return result; } #if 0 /****************************************************************************** * * * 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; } #endif /* ---------------------------------------------------------------------------------- */ /* IMPLEMENTATION DES FONCTIONS DE CLASSE */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : plugin = greffon à manipuler. * * * * Description : Prend acte de l'activation du greffon. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_pychrysalide_plugin_enable(GPyChrysalidePlugin *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) { g_plugin_module_log_simple_message(G_PLUGIN_MODULE(plugin), LMT_ERROR, _("Can not extend the existing table of Python built-in modules.")); result = false; goto done; } Py_Initialize(); gstate = PyGILState_Ensure(); plugin->py_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. */ // TODO : check (2025) result = (plugin->py_module != NULL); PyGILState_Release(gstate); done: return result; } /****************************************************************************** * * * Paramètres : plugin = greffon à manipuler. * * * * Description : Prend acte de la désactivation du greffon. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_pychrysalide_plugin_disable(GPyChrysalidePlugin *plugin) { PyGILState_STATE gstate; /* Sauvegarde d'environnement */ gstate = PyGILState_Ensure(); clear_all_accesses_to_python_modules(); Py_XDECREF(plugin->py_module); plugin->py_module = NULL; PyGILState_Release(gstate); } /* ---------------------------------------------------------------------------------- */ /* INTERVENTION DANS LA GESTION DE GREFFONS */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * 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 : modname = nom du module à charger. * * filename = chemin d'accès au code Python à charger. * * * * Description : Crée un greffon à partir de code Python. * * * * Retour : Adresse de la structure mise en place ou NULL si erreur. * * * * Remarques : - * * * ******************************************************************************/ static GPluginModule *create_python_plugin(const char *modname, const char *filename) { GPluginModule *result; /* Structure à retourner */ PyObject *name; /* Chemin d'accès pour Python */ PyObject *module; /* Script Python chargé */ PyObject *dict; /* Dictionnaire associé */ PyObject *class; /* Classe à instancier */ PyObject *instance; /* Instance Python du greffon */ name = PyUnicode_FromString(modname); if (name == NULL) goto bad_exit; module = PyImport_Import(name); Py_DECREF(name); if (module == NULL) goto no_import; dict = PyModule_GetDict(module); class = PyDict_GetItemString(dict, "AutoLoad"); if (class == NULL) goto no_class; if (!PyType_Check(class->ob_type)) goto no_class; instance = PyObject_CallFunction(class, NULL); if (instance == NULL) goto no_instance; result = G_PLUGIN_MODULE(pygobject_get(instance)); /** * L'instance Python et l'objet GLib résultant sont un même PyGObject. * * Donc pas besoin de toucher au comptage des références ici, la libération * se réalisera à la fin, quand l'objet GLib sera libéré. */ Py_DECREF(module); return result; no_instance: log_pychrysalide_exception(_("An error occured when building the 'AutoLoad' instance")); no_class: if (class == NULL) log_plugin_simple_message(LMT_ERROR, _("An error occured when looking for the 'AutoLoad': item not found!")); no_import: Py_XDECREF(module); log_pychrysalide_exception(_("An error occured when importing '%s'"), modname); bad_exit: return NULL; } /****************************************************************************** * * * 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 */ /* 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); if (dir != NULL) { closedir(dir); 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; } /* 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; } */ g_plugin_module_log_variadic_message(plugin, LMT_PROCESS, _("Loaded the Python plugin found in the '%s' directory"), filename); printf(" -> BUG // %p\n", pyplugin); printf(" -> BUG // %u\n", ((GObject *)pyplugin)->ref_count); //register_plugin(pyplugin); /////////unref_object(pyplugin); done_with_plugin: free(filename); free(modname); } closedir(dir); } free(paths); } /****************************************************************************** * * * Paramètres : plugin = interface à manipuler. * * * * Description : Accompagne la fin du chargement des modules natifs. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_pychrysalide_plugin_handle_native_plugins_loaded_event(GPyChrysalidePlugin *plugin) { PyGILState_STATE gstate; /* Sauvegarde d'environnement */ gstate = PyGILState_Ensure(); load_python_plugins(G_PLUGIN_MODULE(plugin)); PyGILState_Release(gstate); } /* ---------------------------------------------------------------------------------- */ /* POINT D'ENTREE POUR PYTHON */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : - * * * * Description : Point d'entrée pour l'initialisation de Python. * * * * Retour : ? * * * * Remarques : - * * * ******************************************************************************/ PyMODINIT_FUNC PyInit_pychrysalide(void) { PyObject *result; /* Module Python à retourner */ pyinit_details_t details; /* Détails de chargement */ details.standalone = _standalone; details.populate_extra = NULL; result = init_python_pychrysalide_module(&details); return result; }