diff options
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/pe/core-int.h | 56 | ||||
-rw-r--r-- | plugins/pe/core.c | 280 | ||||
-rw-r--r-- | plugins/pe/core.h | 14 | ||||
-rw-r--r-- | plugins/pychrysalide/Makefile.am | 22 | ||||
-rw-r--r-- | plugins/pychrysalide/bindings.c | 1090 | ||||
-rw-r--r-- | plugins/pychrysalide/bindings.h | 68 | ||||
-rw-r--r-- | plugins/pychrysalide/core-int.h | 58 | ||||
-rw-r--r-- | plugins/pychrysalide/core-ui-int.h | 56 | ||||
-rw-r--r-- | plugins/pychrysalide/core-ui.c | 318 | ||||
-rw-r--r-- | plugins/pychrysalide/core-ui.h | 64 | ||||
-rw-r--r-- | plugins/pychrysalide/core.c | 1439 | ||||
-rw-r--r-- | plugins/pychrysalide/core.h | 30 | ||||
-rw-r--r-- | plugins/pychrysalide/helpers.c | 150 | ||||
-rw-r--r-- | plugins/pychrysalide/helpers.h | 38 | ||||
-rw-r--r-- | plugins/pychrysalide/plugins/Makefile.am | 4 | ||||
-rw-r--r-- | plugins/pychrysalide/plugins/module.c | 11 | ||||
-rw-r--r-- | plugins/pychrysalide/plugins/plugin.c | 958 | ||||
-rw-r--r-- | plugins/pychrysalide/plugins/plugin.h | 7 | ||||
-rw-r--r-- | plugins/pychrysalide/plugins/python-int.h | 58 | ||||
-rw-r--r-- | plugins/pychrysalide/plugins/python.c | 489 | ||||
-rw-r--r-- | plugins/pychrysalide/plugins/python.h | 57 |
21 files changed, 3672 insertions, 1595 deletions
diff --git a/plugins/pe/core-int.h b/plugins/pe/core-int.h new file mode 100644 index 0000000..1d1b817 --- /dev/null +++ b/plugins/pe/core-int.h @@ -0,0 +1,56 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * core-int.h - prototypes internes pour l'intégration du support du format PE + * + * Copyright (C) 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 <http://www.gnu.org/licenses/>. + */ + + +#ifndef _PLUGINS_PE_CORE_INT_H +#define _PLUGINS_PE_CORE_INT_H + + +#include "core.h" + + +#include <plugins/native-int.h> + + + +/* Greffon natif pour le support du format PE (instance) */ +struct _GPePlugin +{ + GNativePlugin parent; /* A laisser en premier */ + +}; + + +/* Greffon natif pour le support du format PE (classe) */ +struct _GPePluginClass +{ + GNativePluginClass parent; /* A laisser en premier */ + +}; + + +/* Met en place un module pour un greffon de support PE. */ +bool g_pe_plugin_create(GPePlugin *, GModule *); + + + +#endif /* _PLUGINS_PE_CORE_INT_H */ diff --git a/plugins/pe/core.c b/plugins/pe/core.c index e8e6f5e..42f712d 100644 --- a/plugins/pe/core.c +++ b/plugins/pe/core.c @@ -24,35 +24,244 @@ #include "core.h" -#include <core/global.h> +//#include <core/global.h> #include <plugins/self.h> +#include "core-int.h" #include "format.h" #ifdef INCLUDE_PYTHON3_BINDINGS # include "python/module.h" #endif + +/* ---------------------- COMPOSITION DE NOUVEAU GREFFON NATIF ---------------------- */ + + +/* Initialise la classe des greffons de support PE. */ +static void g_pe_plugin_class_init(GPePluginClass *); + +/* Procède à l'initialisation de l'interface de gestion. */ +//static void g_pe_plugin_plugin_manager_interface_init(GPluginManagerInterface *); + +/* Initialise une instance de greffon de support PE. */ +static void g_pe_plugin_init(GPePlugin *); + +/* Supprime toutes les références externes. */ +static void g_pe_plugin_dispose(GPePlugin *); + +/* Procède à la libération totale de la mémoire. */ +static void g_pe_plugin_finalize(GPePlugin *); + + + +/* --------------------- IMPLEMENTATION DES FONCTIONS DE CLASSE --------------------- */ + + +/* Prend acte de l'activation du greffon. */ +static bool g_pe_plugin_enable(GPePlugin *); + +/* Prend acte de l'extinction du greffon. */ +static void g_pe_plugin_disable(GPePlugin *); + + + +/* ---------------------------------------------------------------------------------- */ +/* COMPOSITION DE NOUVEAU GREFFON NATIF */ +/* ---------------------------------------------------------------------------------- */ + + +/* Indique le type défini pour un greffon de liaison Python */ +G_DEFINE_TYPE_WITH_CODE(GPePlugin, g_pe_plugin, G_TYPE_NATIVE_PLUGIN, + /*G_IMPLEMENT_INTERFACE(G_TYPE_PLUGIN_MANAGER, g_pe_plugin_plugin_manager_interface_init)*/); + + +NATIVE_PLUGIN_ENTRYPOINT(g_pe_plugin_new); + + +/****************************************************************************** +* * +* Paramètres : class = classe à initialiser. * +* * +* Description : Initialise la classe des greffons de support PE. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_pe_plugin_class_init(GPePluginClass *class) +{ + GObjectClass *object; /* Autre version de la classe */ + GPluginModuleClass *plugin; /* Version parente de la classe*/ + + object = G_OBJECT_CLASS(class); + + object->dispose = (GObjectFinalizeFunc/* ! */)g_pe_plugin_dispose; + object->finalize = (GObjectFinalizeFunc)g_pe_plugin_finalize; + + plugin = G_PLUGIN_MODULE_CLASS(class); + + plugin->enable = (pg_management_fc)g_pe_plugin_enable; + plugin->disable = (pg_management_fc)g_pe_plugin_disable; + +} + +#if 0 + +/****************************************************************************** +* * +* Paramètres : iface = interface GLib à initialiser. * +* * +* Description : Procède à l'initialisation de l'interface de gestion. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_pe_plugin_plugin_manager_interface_init(GPluginManagerInterface *iface) +{ + iface->handle_native = (handle_native_plugins_cb)g_pe_plugin_handle_native_plugins_loaded_event; + +} + +#endif + + +/****************************************************************************** +* * +* Paramètres : plugin = instance à initialiser. * +* * +* Description : Initialise une instance de greffon de support PE. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_pe_plugin_init(GPePlugin *plugin) +{ + STORE_PLUGIN_ABI(plugin); + +} + + +/****************************************************************************** +* * +* Paramètres : plugin = instance d'objet GLib à traiter. * +* * +* Description : Supprime toutes les références externes. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_pe_plugin_dispose(GPePlugin *plugin) +{ + G_OBJECT_CLASS(g_pe_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_pe_plugin_finalize(GPePlugin *plugin) +{ + G_OBJECT_CLASS(g_pe_plugin_parent_class)->finalize(G_OBJECT(plugin)); + +} + + +/****************************************************************************** +* * +* Paramètres : filename = nom du fichier à charger. * +* * +* Description : Crée un module pour un greffon de support PE. * +* * +* Retour : Adresse de la structure mise en place. * +* * +* Remarques : Le transfert de propriétée du module est total. * +* * +******************************************************************************/ + +GPluginModule *g_pe_plugin_new(GModule *module) +{ + GPePlugin *result; /* Structure à retourner */ + + result = g_object_new(G_TYPE_PE_PLUGIN, NULL); + + if (!g_pe_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 PE. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : Le transfert de propriétée du module est total. * +* * +******************************************************************************/ + +bool g_pe_plugin_create(GPePlugin *plugin, GModule *module) +{ + bool result; /* Bilan à retourner */ + #ifdef INCLUDE_PYTHON3_BINDINGS -# define PG_REQ RL("PyChrysalide") +# define PG_REQ REQ_LIST("PyChrysalide") #else # define PG_REQ NO_REQ #endif + result = g_native_plugin_create(G_NATIVE_PLUGIN(plugin), + "PeFmt", + "PE format support", + PACKAGE_VERSION, + CHRYSALIDE_WEBSITE("doc/formats"), + PG_REQ, + module); + return result; + +} -DEFINE_CHRYSALIDE_PLUGIN("Pe", "PE format support", - PACKAGE_VERSION, CHRYSALIDE_WEBSITE("doc/formats"), - PG_REQ, AL(PGA_PLUGIN_INIT));//, PGA_CONTENT_RESOLVER)); +/* ---------------------------------------------------------------------------------- */ +/* IMPLEMENTATION DES FONCTIONS DE CLASSE */ +/* ---------------------------------------------------------------------------------- */ + /****************************************************************************** * * * Paramètres : plugin = greffon à manipuler. * * * -* Description : Prend acte du chargement du greffon. * +* Description : Prend acte de l'activation du greffon. * * * * Retour : - * * * @@ -60,7 +269,7 @@ DEFINE_CHRYSALIDE_PLUGIN("Pe", "PE format support", * * ******************************************************************************/ -G_MODULE_EXPORT bool chrysalide_plugin_init(GPluginModule *plugin) +static bool g_pe_plugin_enable(GPePlugin *plugin) { bool result; /* Bilan à retourner */ @@ -77,6 +286,60 @@ G_MODULE_EXPORT bool chrysalide_plugin_init(GPluginModule *plugin) /****************************************************************************** * * +* Paramètres : plugin = greffon à manipuler. * +* * +* Description : Prend acte de l'extinction du greffon. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_pe_plugin_disable(GPePlugin *plugin) +{ + +} + + + +#if 0 + +/* ---------------------------------------------------------------------------------- */ +/* INTERVENTION DANS LA GESTION DE GREFFONS */ +/* INTERVENTION DANS LA GESTION DE GREFFONS */ +/* INTERVENTION DANS LA GESTION DE GREFFONS */ +/* ---------------------------------------------------------------------------------- */ + + +/****************************************************************************** +* * +* Paramètres : plugin = interface à manipuler. * +* * +* Description : Accompagne la fin du chargement des modules natifs. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_pe_plugin_handle_native_plugins_loaded_event(GPePlugin *plugin) +{ + PyGILState_STATE gstate; /* Sauvegarde d'environnement */ + + gstate = PyGILState_Ensure(); + + load_python_plugins(G_PLUGIN_MODULE(plugin)); + + PyGILState_Release(gstate); + +} + + + +/****************************************************************************** +* * * Paramètres : plugin = greffon à manipuler. * * action = type d'action attendue. * * content = contenu binaire à traiter. * @@ -115,3 +378,6 @@ G_MODULE_EXPORT void chrysalide_plugin_handle_binary_content(const GPluginModule } #endif + + +#endif diff --git a/plugins/pe/core.h b/plugins/pe/core.h index 2d9aeb5..5c0696f 100644 --- a/plugins/pe/core.h +++ b/plugins/pe/core.h @@ -2,7 +2,7 @@ /* Chrysalide - Outil d'analyse de fichiers binaires * core.h - prototypes pour l'intégration du support du format PE * - * Copyright (C) 2017-2018 Cyrille Bagard + * Copyright (C) 2017-2025 Cyrille Bagard * * This file is part of Chrysalide. * @@ -25,16 +25,18 @@ #define _PLUGINS_PE_CORE_H +#include <glibext/helpers.h> #include <plugins/plugin.h> -#include <plugins/plugin-int.h> -/* Prend acte du chargement du greffon. */ -G_MODULE_EXPORT bool chrysalide_plugin_init(GPluginModule *); +#define G_TYPE_PE_PLUGIN (g_pe_plugin_get_type()) -/* Procède à une opération liée à un contenu binaire. */ -//G_MODULE_EXPORT void chrysalide_plugin_handle_binary_content(const GPluginModule *, PluginAction, GBinContent *, wgroup_id_t, GtkStatusStack *); +DECLARE_GTYPE(GPePlugin, g_pe_plugin, G, PE_PLUGIN); + + +/* Crée un module pour un greffon de support PE. */ +GPluginModule *g_pe_plugin_new(GModule *); diff --git a/plugins/pychrysalide/Makefile.am b/plugins/pychrysalide/Makefile.am index 183a4ef..7b1a331 100644 --- a/plugins/pychrysalide/Makefile.am +++ b/plugins/pychrysalide/Makefile.am @@ -1,7 +1,7 @@ DEFAULT_INCLUDES = -I$(top_builddir) -idirafter. -lib_LTLIBRARIES = pychrysalide.la +lib_LTLIBRARIES = pychrysalide.la pychrysalideui.la libdir = $(pluginslibdir) @@ -32,7 +32,9 @@ endif pychrysalide_la_SOURCES = \ access.h access.c \ + bindings.h bindings.c \ constants.h constants.c \ + core-int.h \ core.h core.c \ helpers.h helpers.c \ star.h star.c \ @@ -71,6 +73,24 @@ pychrysalide_la_LDFLAGS = \ $(RUN_PATH) +EXTRA_pychrysalideui_la_DEPENDENCIES = pychrysalide.la + +pychrysalideui_la_SOURCES = \ + core-ui-int.h \ + core-ui.h core-ui.c + +pychrysalideui_la_LIBADD = + +# -ldl: dladdr(), dlerror() +pychrysalideui_la_LDFLAGS = \ + -module -avoid-version -ldl \ + $(LIBPYTHON_INTERPRETER_LIBS) \ + $(LIBPYGOBJECT_LIBS) \ + -L.libs -l:pychrysalide.so \ + -L$(top_srcdir)/src/.libs -lchrysacoreui\ + $(RUN_PATH) + + devdir = $(includedir)/chrysalide/$(subdir) dev_HEADERS = $(pychrysalide_la_SOURCES:%c=) diff --git a/plugins/pychrysalide/bindings.c b/plugins/pychrysalide/bindings.c new file mode 100644 index 0000000..295667f --- /dev/null +++ b/plugins/pychrysalide/bindings.c @@ -0,0 +1,1090 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * bindings.c - éléments d'un socle commun aux fonctionnalités graphiques et non graphiques + * + * Copyright (C) 2024 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "bindings.h" + + +#ifdef PYTHON_PACKAGE +# include <dlfcn.h> +#endif +#include <pygobject.h> +#include <stdio.h> + + +#include <config.h> +#include <common/cpp.h> +#include <plugins/pglist.h> // REMME ? +#include <plugins/self.h> // REMME ? + + +#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" + + + + + +/* ------------------------ FONCTIONNALITES POUR CODE PYTHON ------------------------ */ + + +#define PYCHRYSALIDE_NAME "pychrysalide" + +#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." + + +/* 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 *); + + + +/* ------------------------ FONCTIONNALITES DE MISE EN PLACE ------------------------ */ + + +/* 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); + +/* Met en place un environnement pour l'extension Python. */ +static bool setup_python_context(void); + +/* Assure la définition d'un type GObject pour Python adapté. */ +static void ensure_native_pygobject_type(PyTypeObject **); + +/* Fournit la référence à un éventuel module déjà en place. */ +static PyObject *get_existing_modules(void); + +/* Définit les différents modules du support Python. */ +static PyObject *create_basic_modules(void); + +/* Inscrit les défintions des objets Python de Chrysalide. */ +static bool populate_python_modules(const pyinit_details_t *); + +/* Restore une ancienne définition de type GObject au besoin. */ +static void restore_original_pygobject_type(PyTypeObject *); + + +/* ------------------------ FONCTIONS GLOBALES DE CHRYSALIDE ------------------------ */ + + + +/* Point de sortie pour l'initialisation de Python. */ +static void PyExit_pychrysalide(void); + + + + + +/* ---------------------------------------------------------------------------------- */ +/* FONCTIONNALITES POUR CODE PYTHON */ +/* ---------------------------------------------------------------------------------- */ + +/****************************************************************************** +* * +* 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", "x.x.x");// FIXME _chrysalide_plugin.version); + + result = PyUnicode_FromString(version); + + return result; + +} + + + +/* ---------------------------------------------------------------------------------- */ +/* FONCTIONNALITES DE MISE EN PLACE */ +/* ---------------------------------------------------------------------------------- */ + + +/****************************************************************************** +* * +* 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()"); + goto exit; + } + + snprintf(cmds, sizeof(cmds), GRAB_ABI_FLAGS_IN_PYTHON, fds[1]); + + ret = PyRun_SimpleString(cmds); + if (ret != 0) goto exit_with_pipe; + + got = read(fds[0], content, sizeof(content)); + if (got < 0) + { + perror("read()"); + goto exit_with_pipe; + } + + content[got] = '\0'; + + result = (strcmp(content, LIBPYTHON_ABI_FLAGS) == 0); + + exit_with_pipe: + + close(fds[0]); + close(fds[1]); + + 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 <python3-gi>/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 <python3>/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 <python3>/Objects/typeobject.c : + * + * int _PyType_CheckConsistency(PyTypeObject *type) + * { + * [...] + * CHECK(!_PyObject_IsFreed((PyObject *)type)); + * [...] + * } + * + * - cf. _PyObject_IsFreed() dans <python3>/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 <python3-gi>/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, selon la valeur + * portée par PyGObject_Type.ob_base.ob_base.ob_type.tp_name : + * - "type" (PyType_Type) : état initial ; + * - "_GObjectMetaBase" : état revu. + */ + + /** + * PyGObject_Type.ob_base.ob_base.ob_type != &PyType_Type ? + */ + result = (PyType_CheckExact(&PyGObject_Type) == 0); + + if (!result) + { + gi_types_mod = PyImport_ImportModule("gi.types"); + + result = (PyErr_Occurred() == NULL); + + if (result) + result = (PyType_CheckExact(&PyGObject_Type) == 0); + + Py_XDECREF(gi_types_mod); + + } + +#ifndef NDEBUG + if (result) + assert(strcmp(PyGObject_Type.ob_base.ob_base.ob_type->tp_name, "_GObjectMetaBase") == 0); +#endif + + if (!result) + PyErr_SetString(PyExc_SystemError, "unable to install metaclass for Python GObjects."); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Met en place un environnement pour l'extension Python. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool setup_python_context(void) +{ + bool result; /* Bilan à retourner */ + + result = false; + + /** + * Un message d'erreur doit être défini en cas d'échec de l'initialisation, + * via un appel à PyErr_SetString(). + */ + + 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; + + result = true; + + exit: + + 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); + + } + + // TODO : message d'erreur ? + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : py_gobj_def = définition de type actuelle. [OUT] * +* * +* Description : Assure la définition d'un type GObject pour Python adapté. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void ensure_native_pygobject_type(PyTypeObject **py_gobj_def) +{ + GQuark pygobject_class_key; /* Copie d'un accès GI interne */ + + /** + * Les appels suivants procèdent à l'enregistrement de différents éléments + * dans l'espace de noms Python pour Chrysalide. + * + * Une majeure partie de ces éléments est constituée d'objets dérivés de + * GObject. Ce type d'objet (G_TYPE_OBJECT) est représenté par deux types + * en Python : + * + * - gi._gi.GObject, mis en place lors de l'importation du module gi. + * + * Ce dernier lance automatiquement l'importation du module natif gi._gi, + * lequel, via son initialisation dans la fonction PyInit__gi() + * (cf. ./gi/gimodule.c) lance un appel à pyi_object_register_types() + * qui procède à l'enregistrement du type "GObject" porté par la structure + * PyGObject_Type en correspondance au type GLib G_TYPE_OBJECT (cf. appel + * à pygobject_register_class() dans gi/pygobject-object.c). + * + * - gi.repository.GObject.Object, qui vient surclasser le type précédent + * lors d'un appel d'initialisation : from gi.repository import GObject. + * + * Cette seconde définition est destinée à apporter une représentation + * de l'introspection GObject de plus haut niveau pour l'utilisateur par + * rapport à celle de bas niveau gi._gi. + * + * Il demeure que la seconde définition est entièrement implémentée en Python + * et porte ainsi le fanion Py_TPFLAGS_HEAPTYPE, imposant cette même dernière + * propriétée à tous les objets qui en dérivent. + * + * Les définitions de Chrysalide sont cependant toutes statiques et donc + * incompatibles avec une définition gi.repository.GObject.Object, comme le + * pointent les validations opérées par PyType_Ready(). + * + * Une solution consiste ici à restaurer au besoin la définition gi._gi.GObject + * brute, effectuer les enregistrements de Chrysalide sur cette base, et + * remettre en place la définition éventuellement remplacée ensuite. + */ + + *py_gobj_def = pygobject_lookup_class(G_TYPE_OBJECT); + + if (*py_gobj_def != &PyGObject_Type) + { + Py_INCREF((PyObject *)*py_gobj_def); + + /* Définition récupérée de pyi_object_register_types() */ + pygobject_class_key = g_quark_from_static_string("PyGObject::class"); + + g_type_set_qdata(G_TYPE_OBJECT, pygobject_class_key, &PyGObject_Type); + + } + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Fournit la référence à un éventuel module déjà en place. * +* * +* Retour : Pointeur vers le module mis en place. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *get_existing_modules(void) +{ + PyObject *result; /* Module Python à retourner */ + + /** + * 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(PYCHRYSALIDE_NAME); + + Py_XINCREF(result); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Définit les différents modules du support Python. * +* * +* Retour : Pointeur vers le module mis en place. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *create_basic_modules(void) +{ + PyObject *result; /* Module Python à retourner */ + bool status; /* Bilan des inclusions */ + + 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_NAME, + .m_doc = PYCHRYSALIDE_DOC, + + .m_size = -1, + + .m_methods = py_chrysalide_methods + + }; + + result = PyModule_Create(&py_chrysalide_module); + + register_access_to_python_module(py_chrysalide_module.m_name, result); + + status = true; + + /** + * Réceptacle pour objets et constantes : à laisser en premier ajout donc. + */ + if (status) status = add_features_module(result); + + if (status) status = define_data_types_constants(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) + { + Py_DECREF(result); + result = NULL; + + } + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : details = précisions de chargement complémentaires. * +* * +* Description : Inscrit les défintions des objets Python de Chrysalide. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool populate_python_modules(const pyinit_details_t *details) +{ + bool result; /* Bilan à retourner */ + + /** + * Les chargements de types supplémentaires, apportés par la version UI, ne + * peuvent être forcés depuis les mises en places des versions non-UI via + * les classiques appels ensure_xxx(). + * + * L'assurance d'un chargement préalable est ainsi réalisée ici, via + * un chargement préliminaire, si besoin est. + */ + + if (details->populate_extra) + result = details->populate_extra(); + else + result = true; + + /* + if (result) result = ensure_python_string_enum_is_registered(); + */ + if (result) result = ensure_python_py_struct_is_registered(); + + if (result) result = populate_analysis_module(); + if (result) result = populate_arch_module(); + if (result) result = populate_glibext_module(); + if (result) result = populate_common_module(); + if (result) result = populate_core_module(); + /* + if (result) result = populate_debug_module(); + */ + if (result) result = populate_format_module(); + /* +#ifdef INCLUDE_GTK_SUPPORT + if (result) result = populate_gtkext_module(); + if (result) result = populate_gui_module(); +#endif + if (result) result = populate_mangling_module(); + */ + if (result) result = populate_plugins_module(); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : py_gobj_def = définition de type actuelle. [OUT] * +* * +* Description : Restore une ancienne définition de type GObject au besoin. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +void restore_original_pygobject_type(PyTypeObject *py_gobj_def) +{ + GQuark pygobject_class_key; /* Copie d'un accès GI interne */ + + if (py_gobj_def != &PyGObject_Type) + { + /* Définition récupérée de pyi_object_register_types() */ + pygobject_class_key = g_quark_from_static_string("PyGObject::class"); + + g_type_set_qdata(G_TYPE_OBJECT, pygobject_class_key, py_gobj_def); + + Py_DECREF((PyObject *)py_gobj_def); + + } + +} + + +/****************************************************************************** +* * +* Paramètres : details = précisions de chargement complémentaires. * +* * +* Description : Implémente le point d'entrée pour l'initialisation de Python.* +* * +* Retour : Module mis en place ou NULL en cas d'échec. * +* * +* Remarques : - * +* * +******************************************************************************/ + +PyObject *init_python_pychrysalide_module(const pyinit_details_t *details) +{ + PyObject *result; /* Module Python à retourner */ + PyTypeObject *py_gobj_def; /* Définition GObject courante */ + bool status; /* Bilan des inclusions */ + + result = get_existing_modules(); + + if (result != NULL) + return result; + + if (!setup_python_context()) + 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 : [<class 'gobject.GBoxed'>, <class 'object'>] + * + * - avec module GLib : [<class 'gi.repository.GLib.Checksum'>, <class 'gi.Boxed'>, <class 'gobject.GBoxed'>, <class 'object'>] + * + * 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) : + * + * <frozen importlib._bootstrap>: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; + + /* Mise en place des fonctionnalités offertes */ + + ensure_native_pygobject_type(&py_gobj_def); + + result = create_basic_modules(); + + if (result == NULL) + PyErr_SetString(PyExc_SystemError, "failed to create all PyChrysalide modules."); + + else + { + status = populate_python_modules(details); + + if (!status) + PyErr_SetString(PyExc_SystemError, "failed to load all PyChrysalide components."); + + else if (details->standalone) + status = do_global_init(); + + if (!status) + { + Py_DECREF(result); + result = NULL; + } + + } + + restore_original_pygobject_type(py_gobj_def); + + exit: + + if (result == NULL && !details->standalone) + /*log_pychrysalide_exception("Loading failed")*/; + + return result; + +} + + + +/* ---------------------------------------------------------------------------------- */ +/* FONCTIONS GLOBALES DE CHRYSALIDE */ +/* ---------------------------------------------------------------------------------- */ + + + + +/****************************************************************************** +* * +* Paramètres : py_gobj_def = définition de type actuelle. [OUT] * +* * +* Description : Restore une ancienne définition de type GObject au besoin. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +bool do_global_init(void) +{ + + return true; + return false; + + +#if 0 + + + bool result; /* Bilan à retourner */ + 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 */ + + ret = Py_AtExit(PyExit_pychrysalide); + if (ret == -1) + { + PyErr_SetString(PyExc_SystemError, "failed to register a cleanup function."); + goto exit_and_restore; + } + + /** + * 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"); + + + // err msg + + + Py_DECREF(result); + result = NULL; + + goto exit_and_restore; + } + + self = g_plugin_module_new(info.dli_fname); + assert(self != NULL); + + register_plugin(self); + +#endif + + + if (!load_core_components(ACC_GLOBAL_VARS)) + { + PyErr_SetString(PyExc_SystemError, "unable to load core components."); + goto exit; + } + + 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); + + unref_object(self); + + unlock_plugin_list_for_reading(); + + load_remaning_plugins(); + + + + + + done: + + 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 + +} diff --git a/plugins/pychrysalide/bindings.h b/plugins/pychrysalide/bindings.h new file mode 100644 index 0000000..1c63956 --- /dev/null +++ b/plugins/pychrysalide/bindings.h @@ -0,0 +1,68 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * bindings.h - prototypes pour les éléments d'un socle commun aux fonctionnalités graphiques et non graphiques + * + * Copyright (C) 2024 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef _PLUGINS_PYCHRYSALIDE_BINDINGS_H +#define _PLUGINS_PYCHRYSALIDE_BINDINGS_H + + +/** + * Note: + * Since Python may define some pre-processor definitions which affect the standard headers + * on some systems, you must include Python.h before any standard headers are included. + * + * cf. https://docs.python.org/3.4/c-api/intro.html + */ +#include <Python.h> + + +#include <stdbool.h> + + + +/* Charge un module GI dans Python avec une version attendue. */ +bool import_namespace_from_gi_repository(const char *, const char *); + +/* Raffinements pour la mise en place du module Python */ +typedef struct _pyinit_details_t +{ + bool standalone; /* Chargement depuis Python ? */ + + + bool (* populate_extra) (void); /* Ajout de types ? */ + + +} pyinit_details_t; + +/* Implémente le point d'entrée pour l'initialisation de Python. */ +PyObject *init_python_pychrysalide_module(const pyinit_details_t *); + + + + + +bool do_global_init(void); + + + +#endif /* _PLUGINS_PYCHRYSALIDE_BINDINGS_H */ diff --git a/plugins/pychrysalide/core-int.h b/plugins/pychrysalide/core-int.h new file mode 100644 index 0000000..2b8fcc8 --- /dev/null +++ b/plugins/pychrysalide/core-int.h @@ -0,0 +1,58 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * core-int.h - prototypes internes pour le plugin permettant des extensions en Python + * + * Copyright (C) 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 <http://www.gnu.org/licenses/>. + */ + + +#ifndef _PLUGINS_PYCHRYSALIDE_CORE_INT_H +#define _PLUGINS_PYCHRYSALIDE_CORE_INT_H + + +#include "core.h" + + +#include <plugins/native-int.h> + + + +/* Greffon natif pour la liaison Python de Chrysalide (instance) */ +struct _GPyChrysalidePlugin +{ + GNativePlugin parent; /* A laisser en premier */ + + PyObject *py_module; /* Réceptacle de chargement */ + +}; + + +/* Greffon natif pour la liaison Python de Chrysalide (classe) */ +struct _GPyChrysalidePluginClass +{ + GNativePluginClass parent; /* A laisser en premier */ + +}; + + +/* Met en place un module pour un greffon de support Python. */ +bool g_pychrysalide_plugin_create(GPyChrysalidePlugin *, GModule *); + + + +#endif /* _PLUGINS_PYCHRYSALIDE_CORE_INT_H */ diff --git a/plugins/pychrysalide/core-ui-int.h b/plugins/pychrysalide/core-ui-int.h new file mode 100644 index 0000000..7cecc13 --- /dev/null +++ b/plugins/pychrysalide/core-ui-int.h @@ -0,0 +1,56 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * core-ui-int.h - prototypes internes pour le plugin permettant des extensions UI en Python + * + * Copyright (C) 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 <http://www.gnu.org/licenses/>. + */ + + +#ifndef _PLUGINS_PYCHRYSALIDE_CORE_UI_INT_H +#define _PLUGINS_PYCHRYSALIDE_CORE_UI_INT_H + + +#include "core-ui.h" + + +#include "core-int.h" + + + +/* Greffon natif pour la liaison Python de Chrysalide sous forme graphique (instance) */ +struct _GPyChrysalidePluginUI +{ + GPyChrysalidePlugin parent; /* A laisser en premier */ + +}; + + +/* Greffon natif pour la liaison Python de Chrysalide sous forme graphique (classe) */ +struct _GPyChrysalidePluginUIClass +{ + GPyChrysalidePluginClass parent; /* A laisser en premier */ + +}; + + +/* Met en place un module pour un greffon de support Python. */ +bool g_pychrysalide_plugin_ui_create(GPyChrysalidePluginUI *, GModule *); + + + +#endif /* _PLUGINS_PYCHRYSALIDE_CORE_UI_INT_H */ diff --git a/plugins/pychrysalide/core-ui.c b/plugins/pychrysalide/core-ui.c new file mode 100644 index 0000000..32d3516 --- /dev/null +++ b/plugins/pychrysalide/core-ui.c @@ -0,0 +1,318 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * core-ui.c - plugin permettant des extensions UI en Python + * + * Copyright (C) 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 <http://www.gnu.org/licenses/>. + */ + + +#include "core-ui.h" + + +#include <stdbool.h> + + +#include <i18n.h> +#include <plugins/self.h> + + +#include "bindings.h" +#include "core-ui-int.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_ui_class_init(GPyChrysalidePluginUIClass *); + +/* Initialise une instance de greffon de support Python. */ +static void g_pychrysalide_plugin_ui_init(GPyChrysalidePluginUI *); + +/* Supprime toutes les références externes. */ +static void g_pychrysalide_plugin_ui_dispose(GPyChrysalidePluginUI *); + +/* Procède à la libération totale de la mémoire. */ +static void g_pychrysalide_plugin_ui_finalize(GPyChrysalidePluginUI *); + + + +/* --------------------- IMPLEMENTATION DES FONCTIONS DE CLASSE --------------------- */ + + +/* Prend acte de l'activation du greffon. */ +static bool g_pychrysalide_plugin_ui_enable(GPyChrysalidePluginUI *); + + + +/* ---------------------------------------------------------------------------------- */ +/* COMPOSITION DE NOUVEAU GREFFON NATIF */ +/* ---------------------------------------------------------------------------------- */ + + +/* Indique le type défini pour un greffon de liaison Python */ +G_DEFINE_TYPE(GPyChrysalidePluginUI, g_pychrysalide_plugin_ui, G_TYPE_PYCHRYSALIDE_PLUGIN); + + +NATIVE_PLUGIN_ENTRYPOINT(g_pychrysalide_plugin_ui_new); + + +/****************************************************************************** +* * +* Paramètres : class = classe à initialiser. * +* * +* Description : Initialise la classe des greffons de support Python. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_pychrysalide_plugin_ui_class_init(GPyChrysalidePluginUIClass *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_ui_dispose; + object->finalize = (GObjectFinalizeFunc)g_pychrysalide_plugin_ui_finalize; + + plugin = G_PLUGIN_MODULE_CLASS(class); + + plugin->enable = (pg_management_fc)g_pychrysalide_plugin_ui_enable; + +} + + +/****************************************************************************** +* * +* Paramètres : plugin = instance à initialiser. * +* * +* Description : Initialise une instance de greffon de support Python. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_pychrysalide_plugin_ui_init(GPyChrysalidePluginUI *plugin) +{ + +} + + +/****************************************************************************** +* * +* Paramètres : plugin = instance d'objet GLib à traiter. * +* * +* Description : Supprime toutes les références externes. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_pychrysalide_plugin_ui_dispose(GPyChrysalidePluginUI *plugin) +{ + G_OBJECT_CLASS(g_pychrysalide_plugin_ui_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_ui_finalize(GPyChrysalidePluginUI *plugin) +{ + G_OBJECT_CLASS(g_pychrysalide_plugin_ui_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_ui_new(GModule *module) +{ + GPyChrysalidePluginUI *result; /* Structure à retourner */ + + result = g_object_new(G_TYPE_PYCHRYSALIDE_PLUGIN, NULL); + + if (!g_pychrysalide_plugin_ui_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_ui_create(GPyChrysalidePluginUI *plugin, GModule *module) +{ + bool result; /* Bilan à retourner */ + + result = g_pychrysalide_plugin_create(G_PYCHRYSALIDE_PLUGIN(plugin), module); + + return result; + +} + + + +/* ---------------------------------------------------------------------------------- */ +/* 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_ui_enable(GPyChrysalidePluginUI *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_pychrysalideui); + + 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(); + + G_PYCHRYSALIDE_PLUGIN(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 = (G_PYCHRYSALIDE_PLUGIN(plugin)->py_module != NULL); + + PyGILState_Release(gstate); + + done: + + return result; + +} + + + +/* ---------------------------------------------------------------------------------- */ +/* POINT D'ENTREE POUR PYTHON */ +/* ---------------------------------------------------------------------------------- */ + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Point d'entrée pour l'initialisation de Python. * +* * +* Retour : ? * +* * +* Remarques : - * +* * +******************************************************************************/ + +PyMODINIT_FUNC PyInit_pychrysalideui(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; + +} diff --git a/plugins/pychrysalide/core-ui.h b/plugins/pychrysalide/core-ui.h new file mode 100644 index 0000000..658aa19 --- /dev/null +++ b/plugins/pychrysalide/core-ui.h @@ -0,0 +1,64 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * core-ui.h - prototypes pour le plugin permettant des extensions UI en Python + * + * Copyright (C) 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 <http://www.gnu.org/licenses/>. + */ + + +#ifndef _PLUGINS_PYCHRYSALIDE_CORE_UI_H +#define _PLUGINS_PYCHRYSALIDE_CORE_UI_H + + +/** + * Note: + * Since Python may define some pre-processor definitions which affect the standard headers + * on some systems, you must include Python.h before any standard headers are included. + * + * cf. https://docs.python.org/3.4/c-api/intro.html + */ +#include <Python.h> + + +#include <glibext/helpers.h> +#include <plugins/plugin.h> + + + +/* ---------------------- COMPOSITION DE NOUVEAU GREFFON NATIF ---------------------- */ + + +#define G_TYPE_PYCHRYSALIDE_PLUGIN_UI (g_pychrysalide_plugin_ui_get_type()) + +DECLARE_GTYPE(GPyChrysalidePluginUI, g_pychrysalide_plugin_ui, G, PYCHRYSALIDE_PLUGIN_UI); + + +/* Crée un module pour un greffon de support Python. */ +GPluginModule *g_pychrysalide_plugin_ui_new(GModule *); + + + +/* --------------------------- POINT D'ENTREE POUR PYTHON --------------------------- */ + + +/* Point d'entrée pour l'initialisation de Python. */ +PyMODINIT_FUNC PyInit_pychrysalideui(void); + + + +#endif /* _PLUGINS_PYCHRYSALIDE_CORE_UI_H */ diff --git a/plugins/pychrysalide/core.c b/plugins/pychrysalide/core.c index f63fd9e..e815e25 100644 --- a/plugins/pychrysalide/core.c +++ b/plugins/pychrysalide/core.c @@ -2,7 +2,7 @@ /* Chrysalide - Outil d'analyse de fichiers binaires * core.c - plugin permettant des extensions en Python * - * Copyright (C) 2018-2019 Cyrille Bagard + * Copyright (C) 2018-2025 Cyrille Bagard * * This file is part of Chrysalide. * @@ -21,6 +21,15 @@ */ +#include "core.h" + + +#include "core-int.h" + + + + + #undef NO_IMPORT_PYGOBJECT #include <pygobject.h> #define NO_IMPORT_PYGOBJECT @@ -32,9 +41,8 @@ #include <assert.h> #include <errno.h> #include <malloc.h> -#include <pygobject.h> +//#include <pygobject.h> #include <stdarg.h> -#include <stdio.h> #include <stdbool.h> #include <string.h> #include <unistd.h> @@ -42,826 +50,568 @@ #include <i18n.h> #include <gleak.h> -#include <common/cpp.h> #include <common/environment.h> #include <common/extstr.h> #include <core/core.h> #include <core/logs.h> #include <core/paths.h> -#include <plugins/pglist.h> +#include <plugins/manager-int.h> +#include <plugins/plugin.h> #include <plugins/self.h> + + #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)); +#include "bindings.h" + /* 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); + + +/* ---------------------- 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 *); -/* Efface un type Python pour greffon de la mémoire. */ -static void free_native_plugin_type(PyTypeObject *); +/* 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 : self = NULL car méthode statique. * -* args = non utilisé ici. * +* Paramètres : class = classe à initialiser. * * * -* Description : Fournit la révision du programme global. * +* Description : Initialise la classe des greffons de support Python. * * * -* Retour : Numéro de révision. * +* Retour : - * * * * Remarques : - * * * ******************************************************************************/ -static PyObject *py_chrysalide_revision(PyObject *self, PyObject *args) +static void g_pychrysalide_plugin_class_init(GPyChrysalidePluginClass *class) { - PyObject *result; /* Valeur à retourner */ + GObjectClass *object; /* Autre version de la classe */ + GPluginModuleClass *plugin; /* Version parente de la classe*/ -#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'." \ -) + object = G_OBJECT_CLASS(class); - result = PyUnicode_FromString("r" XSTR(REVISION)); + object->dispose = (GObjectFinalizeFunc/* ! */)g_pychrysalide_plugin_dispose; + object->finalize = (GObjectFinalizeFunc)g_pychrysalide_plugin_finalize; - return result; + 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 : self = NULL car méthode statique. * -* args = non utilisé ici. * +* Paramètres : iface = interface GLib à initialiser. * * * -* Description : Fournit la version du programme global. * +* Description : Procède à l'initialisation de l'interface de gestion. * * * -* Retour : Numéro de version. * +* Retour : - * * * * Remarques : - * * * ******************************************************************************/ -static PyObject *py_chrysalide_version(PyObject *self, PyObject *args) +static void g_pychrysalide_plugin_plugin_manager_interface_init(GPluginManagerInterface *iface) { - 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 */ + iface->handle_native = (handle_native_plugins_cb)g_pychrysalide_plugin_handle_native_plugins_loaded_event; -#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); +/****************************************************************************** +* * +* Paramètres : plugin = instance à initialiser. * +* * +* Description : Initialise une instance de greffon de support Python. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ - result = PyUnicode_FromString(version); +static void g_pychrysalide_plugin_init(GPyChrysalidePlugin *plugin) +{ + STORE_PLUGIN_ABI(plugin); - return result; + plugin->py_module = NULL; } /****************************************************************************** * * -* Paramètres : self = NULL car méthode statique. * -* args = non utilisé ici. * +* Paramètres : plugin = instance d'objet GLib à traiter. * * * -* Description : Fournit la version du greffon pour Python. * +* Description : Supprime toutes les références externes. * * * -* Retour : Numéro de version. * +* Retour : - * * * * Remarques : - * * * ******************************************************************************/ -static PyObject *py_chrysalide_mod_version(PyObject *self, PyObject *args) +static void g_pychrysalide_plugin_dispose(GPyChrysalidePlugin *plugin) { - 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; + G_OBJECT_CLASS(g_pychrysalide_plugin_parent_class)->dispose(G_OBJECT(plugin)); } /****************************************************************************** * * -* Paramètres : - * +* Paramètres : plugin = instance d'objet GLib à traiter. * * * -* Description : Détermine si l'interpréteur lancé est celui pris en compte. * +* Description : Procède à la libération totale de la mémoire. * * * -* Retour : true si l'exécution peut se poursuivre, false sinon. * +* Retour : - * * * * Remarques : - * * * ******************************************************************************/ -static bool is_current_abi_suitable(void) +static void g_pychrysalide_plugin_finalize(GPyChrysalidePlugin *plugin) { - 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]); + G_OBJECT_CLASS(g_pychrysalide_plugin_parent_class)->finalize(G_OBJECT(plugin)); - 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'; +/****************************************************************************** +* * +* 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. * +* * +******************************************************************************/ - result = (strcmp(content, LIBPYTHON_ABI_FLAGS) == 0); +GPluginModule *g_pychrysalide_plugin_new(GModule *module) +{ + GPyChrysalidePlugin *result; /* Structure à retourner */ - icas_exit: + result = g_object_new(G_TYPE_PYCHRYSALIDE_PLUGIN, NULL); - 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."); + if (!g_pychrysalide_plugin_create(result, module)) + g_clear_object(&result); - return result; + return G_PLUGIN_MODULE(result); } /****************************************************************************** * * -* Paramètres : - * +* Paramètres : plugin = instance à initialiser pleinement. * +* module = module système correspondant. * * * -* Description : Assure une pleine initialisation des objets de Python-GI. * +* Description : Met en place un module pour un greffon de support Python. * * * * Retour : Bilan de l'opération. * * * -* Remarques : - * +* Remarques : Le transfert de propriétée du module est total. * * * ******************************************************************************/ -static bool install_metaclass_for_python_gobjects(void) +bool g_pychrysalide_plugin_create(GPyChrysalidePlugin *plugin, GModule *module) { 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 <python3-gi>/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 <python3>/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 <python3>/Objects/typeobject.c : - * - * int _PyType_CheckConsistency(PyTypeObject *type) - * { - * [...] - * CHECK(!_PyObject_IsFreed((PyObject *)type)); - * [...] - * } - * - * - cf. _PyObject_IsFreed() dans <python3>/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 <python3-gi>/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, selon la valeur - * portée par PyGObject_Type.ob_base.ob_base.ob_type.tp_name : - * - "type" (PyType_Type) : état initial ; - * - "_GObjectMetaBase" : état revu. - */ + 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; + +} + + + - /** - * PyGObject_Type.ob_base.ob_base.ob_type != &PyType_Type ? - */ - result = (PyType_CheckExact(&PyGObject_Type) == 0); - if (!result) - { - gi_types_mod = PyImport_ImportModule("gi.types"); - result = (PyErr_Occurred() == NULL); - if (result) - result = (PyType_CheckExact(&PyGObject_Type) == 0); - Py_XDECREF(gi_types_mod); - } -#ifndef NDEBUG - if (result) - assert(strcmp(PyGObject_Type.ob_base.ob_base.ob_type->tp_name, "_GObjectMetaBase") == 0); -#endif - return result; -} + + +#if 0 + /****************************************************************************** * * -* Paramètres : version = idenfiant de la version de GTK à stipuler. * +* Paramètres : plugin = greffon à manipuler. * +* action = type d'action attendue. * +* type = type d'objet à mettre en place. * * * -* Description : Définit la version attendue de GTK à charger dans Python. * +* Description : Crée une instance à partir d'un type dynamique externe. * * * -* Retour : Bilan de l'opération. * +* Retour : Instance d'objet gérée par l'extension ou NULL. * * * * Remarques : - * * * ******************************************************************************/ -#ifdef INCLUDE_GTK_SUPPORT -static bool set_version_for_gtk_namespace(const char *version) + +G_MODULE_EXPORT gpointer chrysalide_plugin_build_type_instance(GPluginModule *plugin, PluginAction action, GType type) { - bool result; /* Bilan à retourner */ - PyObject *gi_mod; /* Module Python-GObject */ - PyObject *args; /* Arguments à fournir */ + gpointer result; /* Instance à retourner */ + PyGILState_STATE gstate; /* Sauvegarde d'environnement */ + PyTypeObject *pytype; /* Classe Python concernée */ + PyObject *instance; /* Initialisation forcée */ - result = false; + result = NULL; - /** - * 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. - * - */ + gstate = PyGILState_Ensure(); - gi_mod = PyImport_ImportModule("gi"); + pytype = pygobject_lookup_class(type); - if (gi_mod != NULL) + if (pytype != NULL) { - args = Py_BuildValue("ss", "Gtk", version); - - run_python_method(gi_mod, "require_version", args); - - result = (PyErr_Occurred() == NULL); + instance = PyObject_CallObject((PyObject *)pytype, NULL); + assert(instance != NULL); - Py_DECREF(args); - Py_DECREF(gi_mod); + result = pygobject_get(instance); } + PyGILState_Release(gstate); + return result; } + #endif + + /****************************************************************************** * * -* Paramètres : - * +* Paramètres : prefix = message d'introduction à faire apparaître à l'écran.* * * -* Description : Point de sortie pour l'initialisation de Python. * +* Description : Présente dans le journal une exception survenue. * * * -* Retour : ? * +* Retour : - * * * * Remarques : - * * * ******************************************************************************/ -static void PyExit_pychrysalide(void) +void log_pychrysalide_exception(const char *prefix, ...) { - assert(_standalone); - - /* - extern void set_current_project(void *project); + 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 */ - set_current_project(NULL); - */ + assert(PyGILState_Check() == 1); -#ifdef TRACK_GOBJECT_LEAKS - remember_gtypes_for_leaks(); -#endif + if (PyErr_Occurred()) + { + /* Base de la communication */ - exit_all_plugins(); + va_start(ap, prefix); - //unload_all_core_components(true); + vasprintf(&msg, prefix, ap); -#ifdef TRACK_GOBJECT_LEAKS - dump_remaining_gtypes(); -#endif + va_end(ap); -} + /* Détails complémentaires */ + PyErr_Fetch(&err_type, &err_value, &err_traceback); -/****************************************************************************** -* * -* Paramètres : - * -* * -* Description : Point d'entrée pour l'initialisation de Python. * -* * -* Retour : ? * -* * -* Remarques : - * -* * -******************************************************************************/ + PyErr_NormalizeException(&err_type, &err_value, &err_traceback); -#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." + if (err_traceback == NULL) + { + err_traceback = Py_None; + Py_INCREF(err_traceback); + } -PyMODINIT_FUNC PyInit_pychrysalide(void) -{ - PyObject *result; /* Module Python à retourner */ - PyTypeObject *py_gobj_def; /* Définition GObject courante */ - GQuark pygobject_class_key; /* Copie d'un accès GI interne */ - 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 */ + PyException_SetTraceback(err_value, err_traceback); - static PyMethodDef py_chrysalide_methods[] = { - PY_CHRYSALIDE_REVISION_METHOD, - PY_CHRYSALIDE_VERSION_METHOD, - PY_CHRYSALIDE_MOD_VERSION_METHOD, - { NULL } - }; + if (err_value == NULL) + msg = stradd(msg, _(": no extra information is provided...")); - static PyModuleDef py_chrysalide_module = { + else + { + err_string = PyObject_Str(err_value); + err_msg = PyUnicode_AsUTF8(err_string); - .m_base = PyModuleDef_HEAD_INIT, + msg = stradd(msg, ": "); + msg = stradd(msg, err_msg); - .m_name = "pychrysalide", - .m_doc = PYCHRYSALIDE_DOC, + Py_DECREF(err_string); - .m_size = -1, + } - .m_methods = py_chrysalide_methods + /** + * 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); - /** - * 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. - */ + Py_XDECREF(err_traceback); + Py_XDECREF(err_value); + Py_XDECREF(err_type); - result = get_access_to_python_module(py_chrysalide_module.m_name); + log_plugin_simple_message(LMT_ERROR, msg); - if (result != NULL) - { - Py_INCREF(result); - return result; - } + free(msg); - if (!is_current_abi_suitable()) - { - /** - * Un message d'erreur est défini par is_current_abi_suitable() en cas - * d'interpréteur pas adapté. - */ - 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()) - { - PyErr_SetString(PyExc_SystemError, "unable to 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 : [<class 'gobject.GBoxed'>, <class 'object'>] - * - * - avec module GLib : [<class 'gi.repository.GLib.Checksum'>, <class 'gi.Boxed'>, <class 'gobject.GBoxed'>, <class 'object'>] - * - * 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) : - * - * <frozen importlib._bootstrap>: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 0 - if (!import_namespace_from_gi_repository("GLib", "2.0")) - goto exit; -#endif -#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); - /** - * Les appels suivants procèdent à l'enregistrement de différents éléments - * dans l'espace de noms Python pour Chrysalide. - * - * Une majeure partie de ces éléments est constituée d'objets dérivés de - * GObject. Ce type d'objet (G_TYPE_OBJECT) est représenté par deux types - * en Python : - * - * - gi._gi.GObject, mis en place lors de l'importation du module gi. - * - * Ce dernier lance automatiquement l'importation du module natif gi._gi, - * lequel, via son initialisation dans la fonction PyInit__gi() - * (cf. ./gi/gimodule.c) lance un appel à pyi_object_register_types() - * qui procède à l'enregistrement du type "GObject" porté par la structure - * PyGObject_Type en correspondance au type GLib G_TYPE_OBJECT (cf. appel - * à pygobject_register_class() dans gi/pygobject-object.c). - * - * - gi.repository.GObject.Object, qui vient surclasser le type précédent - * lors d'un appel d'initialisation : from gi.repository import GObject. - * - * Cette seconde définition est destinée à apporter une représentation - * de l'introspection GObject de plus haut niveau pour l'utilisateur par - * rapport à celle de bas niveau gi._gi. - * - * Il demeure que la seconde définition est entièrement implémentée en Python - * et porte ainsi le fanion Py_TPFLAGS_HEAPTYPE, imposant cette même dernière - * propriétée à tous les objets qui en dérivent. - * - * Les définitions de Chrysalide sont cependant toutes statiques et donc - * incompatibles avec une définition gi.repository.GObject.Object, comme le - * pointent les validations opérées par PyType_Ready(). - * - * Une solution consiste ici à restaurer au besoin la définition gi._gi.GObject - * brute, effectuer les enregistrements de Chrysalide sur cette base, et - * remettre en place la définition éventuellement remplacée ensuite. - */ - py_gobj_def = pygobject_lookup_class(G_TYPE_OBJECT); - if (py_gobj_def != &PyGObject_Type) - { - Py_INCREF((PyObject *)py_gobj_def); - /* Définition récupérée de pyi_object_register_types() */ - pygobject_class_key = g_quark_from_static_string("PyGObject::class"); - g_type_set_qdata(G_TYPE_OBJECT, pygobject_class_key, &PyGObject_Type); - } - 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_and_restore; - } - 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_and_restore; - } - /** - * 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_and_restore; - } - self = g_plugin_module_new(info.dli_fname); - assert(self != NULL); - register_plugin(self); -#endif +/* ---------------------------------------------------------------------------------- */ +/* IMPLEMENTATION DES FONCTIONS DE CLASSE */ +/* ---------------------------------------------------------------------------------- */ - init_all_plugins(false); - lock_plugin_list_for_reading(); +/****************************************************************************** +* * +* Paramètres : plugin = greffon à manipuler. * +* * +* Description : Prend acte de l'activation du greffon. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ - self = get_plugin_by_name("PyChrysalide", NULL); - assert(self != NULL); +static bool g_pychrysalide_plugin_enable(GPyChrysalidePlugin *plugin) +{ + bool result; /* Bilan à retourner */ + PyGILState_STATE gstate; /* Sauvegarde d'environnement */ + int ret; /* Bilan de préparatifs */ - self_flags = g_plugin_module_get_flags(self); - self_flags &= ~(PSF_FAILURE | PSF_LOADED); - self_flags |= (status ? PSF_LOADED : PSF_FAILURE); + _standalone = false; - g_plugin_module_override_flags(self, self_flags); + /* Chargement du module pour Python */ - unlock_plugin_list_for_reading(); + ret = PyImport_AppendInittab("pychrysalide", &PyInit_pychrysalide); - load_remaning_plugins(); + 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.")); - /** - * 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(). - */ + result = false; + goto done; } - exit_and_restore: + Py_Initialize(); - if (py_gobj_def != &PyGObject_Type) - { - g_type_set_qdata(G_TYPE_OBJECT, pygobject_class_key, py_gobj_def); + gstate = PyGILState_Ensure(); - Py_DECREF((PyObject *)py_gobj_def); + 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) - exit: + result = (plugin->py_module != NULL); + + PyGILState_Release(gstate); - if (result == NULL && !_standalone) - log_pychrysalide_exception("Loading failed"); + 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. * @@ -896,6 +646,85 @@ static void extend_python_path(const char *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)); + + ///result->filename = strdup(filename); + + /** + * 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); + + printf(" -> REF: %p %u\n", result, G_OBJECT(result)->ref_count); + + 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. * @@ -920,7 +749,6 @@ static void load_python_plugins(GPluginModule *plugin) 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 */ @@ -931,19 +759,19 @@ static void load_python_plugins(GPluginModule *plugin) #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); } + free(edir); + #endif g_plugin_module_log_variadic_message(plugin, LMT_INFO, @@ -1007,8 +835,7 @@ static void load_python_plugins(GPluginModule *plugin) goto done_with_plugin; } - //g_plugin_module_create_config(pyplugin); - + /* status = g_plugin_module_manage(pyplugin, PGA_PLUGIN_LOADED); if (!status) @@ -1018,18 +845,19 @@ static void load_python_plugins(GPluginModule *plugin) 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 '<b>%s</b>' directory"), filename); - _register_plugin(pyplugin); + printf(" -> BUG // %p\n", pyplugin); + + printf(" -> BUG // %u\n", ((GObject *)pyplugin)->ref_count); + + //register_plugin(pyplugin); + + /////////unref_object(pyplugin); done_with_plugin: @@ -1049,116 +877,7 @@ static void load_python_plugins(GPluginModule *plugin) /****************************************************************************** * * -* 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. * +* Paramètres : plugin = interface à manipuler. * * * * Description : Accompagne la fin du chargement des modules natifs. * * * @@ -1168,308 +887,48 @@ static void free_native_plugin_type(PyTypeObject *type) * * ******************************************************************************/ -G_MODULE_EXPORT void chrysalide_plugin_on_plugins_loaded(GPluginModule *plugin, PluginAction action) +static void g_pychrysalide_plugin_handle_native_plugins_loaded_event(GPyChrysalidePlugin *plugin) { 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); - - } + load_python_plugins(G_PLUGIN_MODULE(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; - -} +/* ---------------------------------------------------------------------------------- */ +/* POINT D'ENTREE POUR PYTHON */ +/* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * -* Paramètres : namespace = module particulier à charger à partir de gi. * -* version = idenfiant de la version à stipuler. * +* Paramètres : - * * * -* Description : Charge un module GI dans Python avec une version attendue. * +* Description : Point d'entrée pour l'initialisation de Python. * * * -* Retour : Bilan de l'opération. * +* Retour : ? * * * * Remarques : - * * * ******************************************************************************/ -bool import_namespace_from_gi_repository(const char *namespace, const char *version) +PyMODINIT_FUNC PyInit_pychrysalide(void) { - 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); + PyObject *result; /* Module Python à retourner */ + pyinit_details_t details; /* Détails de chargement */ - args_error: + details.standalone = _standalone; - Py_DECREF(args); + details.populate_extra = NULL; - } + result = init_python_pychrysalide_module(&details); 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); - - } - -} diff --git a/plugins/pychrysalide/core.h b/plugins/pychrysalide/core.h index 5d25d3d..60c6c93 100644 --- a/plugins/pychrysalide/core.h +++ b/plugins/pychrysalide/core.h @@ -2,7 +2,7 @@ /* Chrysalide - Outil d'analyse de fichiers binaires * core.h - prototypes pour le plugin permettant des extensions en Python * - * Copyright (C) 2018-2019 Cyrille Bagard + * Copyright (C) 2018-2025 Cyrille Bagard * * This file is part of Chrysalide. * @@ -35,31 +35,29 @@ #include <Python.h> +#include <glibext/helpers.h> #include <plugins/plugin.h> -#include <plugins/plugin-int.h> -/* Point d'entrée pour l'initialisation de Python. */ -PyMODINIT_FUNC PyInit_pychrysalide(void); +/* ---------------------- COMPOSITION DE NOUVEAU GREFFON NATIF ---------------------- */ + + +#define G_TYPE_PYCHRYSALIDE_PLUGIN (g_pychrysalide_plugin_get_type()) -/* Prend acte du chargement du greffon. */ -G_MODULE_EXPORT bool chrysalide_plugin_init(GPluginModule *); +DECLARE_GTYPE(GPyChrysalidePlugin, g_pychrysalide_plugin, G, PYCHRYSALIDE_PLUGIN); -/* Prend acte du déchargement du greffon. */ -G_MODULE_EXPORT void chrysalide_plugin_exit(GPluginModule *); -/* Accompagne la fin du chargement des modules natifs. */ -G_MODULE_EXPORT void chrysalide_plugin_on_plugins_loaded(GPluginModule *, PluginAction); +/* Crée un module pour un greffon de support Python. */ +GPluginModule *g_pychrysalide_plugin_new(GModule *); -/* Crée une instance à partir d'un type dynamique externe. */ -G_MODULE_EXPORT gpointer chrysalide_plugin_build_type_instance(GPluginModule *, PluginAction, GType); -/* Charge un module GI dans Python avec une version attendue. */ -bool import_namespace_from_gi_repository(const char *, const char *); -/* Présente dans le journal une exception survenue. */ -void log_pychrysalide_exception(const char *, ...); +/* --------------------------- POINT D'ENTREE POUR PYTHON --------------------------- */ + + +/* Point d'entrée pour l'initialisation de Python. */ +PyMODINIT_FUNC PyInit_pychrysalide(void); diff --git a/plugins/pychrysalide/helpers.c b/plugins/pychrysalide/helpers.c index deed584..ea2f55d 100644 --- a/plugins/pychrysalide/helpers.c +++ b/plugins/pychrysalide/helpers.c @@ -216,6 +216,53 @@ bool has_python_method(PyObject *module, const char *method) * * * Paramètres : target = propriétaire de la routine visée. * * method = désignation de la fonction à appeler. * +* * +* Description : Indique si une routine Python possède une implémentation. * +* * +* Retour : Bilan de l'analyse. * +* * +* Remarques : - * +* * +******************************************************************************/ + +bool has_python_implementation_method(PyObject *module, const char *method) +{ + bool result; /* Bilan à retourner */ + PyObject *func; /* Fonction visée */ + const PyMethodDef *def; /* Définition de la fonction */ + + result = (PyObject_HasAttrString(module, method) == 1); + + if (result) + { + func = PyObject_GetAttrString(module, method); + assert(func != NULL); + + result = PyCallable_Check(func); + + if (func->ob_type == &PyCFunction_Type) + { + def = ((PyCFunctionObject *)func)->m_ml; + + assert(strcmp(def->ml_name, method) == 0); + + result = (def->ml_meth != not_yet_implemented_method); + + } + + Py_DECREF(func); + + } + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : target = propriétaire de la routine visée. * +* method = désignation de la fonction à appeler. * * args = arguments à associer à l'opération. * * * * Description : Appelle une routine Python. * @@ -1403,6 +1450,109 @@ int convert_to_gdk_rgba(PyObject *arg, void *dst) +/****************************************************************************** +* * +* Paramètres : arg = argument quelconque à tenter de convertir. * +* dst = destination des valeurs récupérées en cas de succès. * +* * +* Description : Tente de convertir en tableau de chaînes de caractères. * +* * +* Retour : Bilan de l'opération, voire indications supplémentaires. * +* * +* Remarques : - * +* * +******************************************************************************/ + +int convert_to_sequence_to_charp_array(PyObject *arg, void *dst) +{ + int result; /* Bilan à retourner */ + charp_array_t *array; /* Tableau à constituer */ + size_t i; /* Boucle de parcours */ + PyObject *value; /* Valeur brute d'un élément */ + + array = (charp_array_t *)dst; + + /* Nettoyage ? */ + if (arg == NULL) + { + result = 1; + goto clean; + } + + else + { + result = 0; + + if (PySequence_Check(arg) != 1) + goto done; + + array->length = PySequence_Length(arg); + + array->values = calloc(array->length, sizeof(char *)); + + for (i = 0; i < array->length; i++) + { + value = PySequence_ITEM(arg, i); + + if (!PyUnicode_Check(value)) + { + Py_DECREF(value); + goto clean; + } + + array->values[i] = strdup(PyUnicode_DATA(value)); + + Py_DECREF(value); + + } + + result = Py_CLEANUP_SUPPORTED; + + } + + done: + + return result; + + clean: + + clean_charp_array(array); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : array = tableau de chaînes de caractères à traiter. * +* * +* Description : Libère de la mémoire un tableau de chaînes de caractères. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +void clean_charp_array(charp_array_t *array) +{ + size_t i; /* Boucle de parcours */ + + for (i = 0; i < array->length; i++) + if (array->values[i] != NULL) + free(array->values[i]); + + if (array->values != NULL) + free(array->values); + + array->values = NULL; + array->length = 0; + +} + + + /* ---------------------------------------------------------------------------------- */ /* TRANSFERT DES VALEURS CONSTANTES */ /* ---------------------------------------------------------------------------------- */ diff --git a/plugins/pychrysalide/helpers.h b/plugins/pychrysalide/helpers.h index ec245aa..133726a 100644 --- a/plugins/pychrysalide/helpers.h +++ b/plugins/pychrysalide/helpers.h @@ -47,6 +47,9 @@ int convert_to_callable(PyObject *, void *); /* Indique si une routine Python existe ou non. */ bool has_python_method(PyObject *, const char *); +/* Indique si une routine Python possède une implémentation. */ +bool has_python_implementation_method(PyObject *, const char *); + /* Appelle une routine Python. */ PyObject *run_python_method(PyObject *, const char *, PyObject *); @@ -179,6 +182,26 @@ static PyObject *py_ ## pyname ## _new(PyTypeObject *type, PyObject *args, PyObj } +/** + * Les initialisations de classes engagées par les appels à pyg_register_class_init() + * ne se déclenchent qu'après les initialisations complètes des classes côté GObject. + * + * Typiquement, pour une déclinaison Python du type PythonModule, sont appelées + * successivement les fonctions suivantes : + * - g_plugin_module_class_init() ; + * - g_python_plugin_class_init() ; + * - py_plugin_module_init_gclass(). + * + * Il est alors impératif de considérer les pointeurs de fonction déjà en place + * afin de ne par remplacer les implémentations de GPythonPlugin par les + * wrappers par défaut de PythonModule. + */ + +#define PY_CLASS_SET_WRAPPER(field, ptr) \ + if (field == NULL) \ + field = ptr; + + /* Marque l'interdiction d'une instanciation depuis Python. */ PyObject *no_python_constructor_allowed(PyTypeObject *, PyObject *, PyObject *); @@ -279,6 +302,21 @@ int convert_to_gdk_rgba(PyObject *, void *); #endif +/* Tableau de chaînes de caractères converti */ +typedef struct _charp_array_t +{ + char **values; /* Liste de chaînes textuelles */ + size_t length; /* Taille de cette liste */ + +} charp_array_t; + +/* Tente de convertir en tableau de chaînes de caractères. */ +int convert_to_sequence_to_charp_array(PyObject *, void *); + +/* Libère de la mémoire un tableau de chaînes de caractères. */ +void clean_charp_array(charp_array_t *); + + /* ----------------------- TRANSFERT DES VALEURS CONSTANTES ------------------------- */ diff --git a/plugins/pychrysalide/plugins/Makefile.am b/plugins/pychrysalide/plugins/Makefile.am index bb9ed5d..0cd6af3 100644 --- a/plugins/pychrysalide/plugins/Makefile.am +++ b/plugins/pychrysalide/plugins/Makefile.am @@ -3,8 +3,10 @@ noinst_LTLIBRARIES = libpychrysaplugins.la libpychrysaplugins_la_SOURCES = \ constants.h constants.c \ - plugin.h plugin.c \ module.h module.c \ + plugin.h plugin.c \ + python-int.h \ + python.h python.c \ translate.h translate.c libpychrysaplugins_la_CFLAGS = $(TOOLKIT_CFLAGS) $(LIBXML_CFLAGS) $(LIBPYTHON_INTERPRETER_CFLAGS) $(LIBPYGOBJECT_CFLAGS) \ diff --git a/plugins/pychrysalide/plugins/module.c b/plugins/pychrysalide/plugins/module.c index 1c7b326..ce94611 100644 --- a/plugins/pychrysalide/plugins/module.c +++ b/plugins/pychrysalide/plugins/module.c @@ -33,8 +33,8 @@ #include <plugins/pglist.h> -#include "constants.h" #include "plugin.h" +#include "python.h" #include "../helpers.h" @@ -46,7 +46,7 @@ static PyObject *py_plugins_get_plugin_by_name(PyObject *, PyObject *); static PyObject *py_plugins_get_all_plugins(PyObject *, PyObject *); /* Fournit les greffons offrant le service demandé. */ -static PyObject *py_plugins_get_all_plugins_for_action(PyObject *, PyObject *); +//static PyObject *py_plugins_get_all_plugins_for_action(PyObject *, PyObject *); @@ -162,7 +162,7 @@ static PyObject *py_plugins_get_all_plugins(PyObject *self, PyObject *args) } - +#if 0 /****************************************************************************** * * * Paramètres : self = NULL car méthode statique. * @@ -219,7 +219,7 @@ static PyObject *py_plugins_get_all_plugins_for_action(PyObject *self, PyObject return result; } - +#endif /****************************************************************************** * * @@ -248,7 +248,7 @@ bool add_plugins_module(PyObject *super) static PyMethodDef py_plugins_methods[] = { PY_PLUGINS_GET_PLUGIN_BY_NAME_METHOD, PY_PLUGINS_GET_ALL_PLUGINS_METHOD, - PY_PLUGINS_GET_ALL_PLUGINS_FOR_ACTION_METHOD, + //PY_PLUGINS_GET_ALL_PLUGINS_FOR_ACTION_METHOD, { NULL } }; @@ -293,6 +293,7 @@ bool populate_plugins_module(void) result = true; if (result) result = ensure_python_plugin_module_is_registered(); + if (result) result = ensure_python_python_plugin_is_registered(); assert(result); diff --git a/plugins/pychrysalide/plugins/plugin.c b/plugins/pychrysalide/plugins/plugin.c index de070cb..48cc992 100644 --- a/plugins/pychrysalide/plugins/plugin.c +++ b/plugins/pychrysalide/plugins/plugin.c @@ -25,14 +25,12 @@ #include "plugin.h" -#include <assert.h> -#include <libgen.h> #include <malloc.h> #include <pygobject.h> #include <string.h> -#include <common/extstr.h> +#include <common/compiler.h> #include <plugins/dt.h> #include <plugins/plugin-int.h> #include <plugins/pglist.h> @@ -52,85 +50,53 @@ /* Initialise la classe des greffons d'extension. */ -static void py_plugin_module_init_gclass(GPluginModuleClass *, gpointer); +static int py_plugin_module_init_gclass(GPluginModuleClass *, PyTypeObject *); CREATE_DYN_ABSTRACT_CONSTRUCTOR(plugin_module, G_TYPE_PLUGIN_MODULE, py_plugin_module_init_gclass); /* Initialise une instance sur la base du dérivé de GObject. */ static int py_plugin_module_init(PyObject *self, PyObject *args, PyObject *kwds); -/* Encadre une étape de la vie d'un greffon. */ -static bool py_plugin_module_manage_wrapper(GPluginModule *); - -/* Assiste la désactivation d'un greffon. */ -static bool py_plugin_module_exit(GPluginModule *); - -/* Accompagne la fin du chargement des modules natifs. */ -static void py_plugin_module_notify_plugins_loaded_wrapper(GPluginModule *, PluginAction); - -/* Fournit le nom brut associé au greffon par défaut. */ -static PyObject *py_plugin_module_get_modname_by_default(PyObject *, PyObject *); +/* Pointe le fichier contenant le greffon manipulé. */ +static char *py_plugin_module_get_filename_wrapper(const GPluginModule *); /* Fournit le nom brut associé au greffon. */ static char *py_plugin_module_get_modname_wrapper(const GPluginModule *); -#if 0 - -#ifdef INCLUDE_GTK_SUPPORT - -/* Complète une liste de resources pour thème. */ -static void py_plugin_module_include_theme_wrapper(const GPluginModule *, PluginAction, gboolean, char ***, size_t *); - -/* Rend compte de la création d'un panneau. */ -static void py_plugin_module_notify_panel_creation_wrapper(const GPluginModule *, PluginAction, GPanelItem *); - -/* Rend compte d'un affichage ou d'un retrait de panneau. */ -static void py_plugin_module_notify_panel_docking_wrapper(const GPluginModule *, PluginAction, GPanelItem *, bool); - -#endif - -/* Procède à une opération liée à un contenu binaire. */ -static void py_plugin_module_handle_binary_content_wrapper(const GPluginModule *, PluginAction, GBinContent *, wgroup_id_t, GtkStatusStack *); - -/* Procède à une opération liée à un contenu chargé. */ -static void py_plugin_module_handle_loaded_content_wrapper(const GPluginModule *, PluginAction, GLoadedContent *, wgroup_id_t, GtkStatusStack *); +/* Prend acte de l'activation du greffon. */ +static bool py_plugin_module_enable_wrapper(GPluginModule *); -/* Procède à une opération liée à l'analyse d'un format. */ -static bool py_plugin_module_handle_known_format_analysis_wrapper(const GPluginModule *, PluginAction, GKnownFormat *, wgroup_id_t, GtkStatusStack *); +/* Prend acte de la désactivation du greffon. */ +static bool py_plugin_module_disable_wrapper(GPluginModule *); -/* Procède à un préchargement de format de fichier. */ -static bool py_plugin_module_preload_binary_format_wrapper(const GPluginModule *, PluginAction, GBinFormat *, GPreloadInfo *, GtkStatusStack *); -/* Procède au rattachement d'éventuelles infos de débogage. */ -static void py_plugin_module_attach_debug_format_wrapper(const GPluginModule *, PluginAction, GExeFormat *); -/* Exécute une action pendant un désassemblage de binaire. */ -static void py_plugin_module_process_disassembly_event_wrapper(const GPluginModule *, PluginAction, GLoadedBinary *, GtkStatusStack *, GProcContext *); - -/* Effectue la détection d'effets d'outils externes. */ -static void py_plugin_module_detect_external_tools_wrapper(const GPluginModule *, PluginAction, const GLoadedContent *, bool, char ***, size_t *); - -#endif +/* ------------------------- MODULE PYTHON POUR LES SCRIPTS ------------------------- */ +/* Affiche un message dans le journal des messages système. */ +static PyObject *py_plugin_module_log_message(PyObject *, PyObject *); -/* ------------------------- MODULE PYTHON POUR LES SCRIPTS ------------------------- */ +/* Indique le nom associé à un greffon. */ +static PyObject *py_plugin_module_get_name(PyObject *, void *); +/* Fournit une description fonctionnelle d'un greffon. */ +static PyObject *py_plugin_module_get_desc(PyObject *, void *); -/* Construit le nom d'un fichier de configuration du greffon. */ -static PyObject *py_plugin_module_build_config_filename(PyObject *, PyObject *); +/* Fournit la version d'un greffon et de ses fonctionnalités. */ +static PyObject *py_plugin_module_get_version(PyObject *, void *); -/* Affiche un message dans le journal des messages système. */ -static PyObject *py_plugin_module_log_message(PyObject *, PyObject *); +/* Fournit l'URL des ressources en ligne liées à un greffon. */ +static PyObject *py_plugin_module_get_url(PyObject *, void *); -/* Fournit le nom brut associé au greffon. */ -static PyObject *py_plugin_module_get_modname(PyObject *, void *); +/* Fournit la liste des dépendances d'un greffon donné. */ +static PyObject *py_plugin_module_get_requirements(PyObject *, void *); /* Indique le fichier contenant le greffon manipulé. */ static PyObject *py_plugin_module_get_filename(PyObject *, void *); -/* Fournit la description du greffon dans son intégralité. */ -static PyObject *py_plugin_module_get_interface(PyObject *, void *); +/* Fournit le nom brut associé au greffon. */ +static PyObject *py_plugin_module_get_modname(PyObject *, void *); @@ -141,47 +107,26 @@ static PyObject *py_plugin_module_get_interface(PyObject *, void *); /****************************************************************************** * * -* Paramètres : class = classe à initialiser. * -* unused = données non utilisées ici. * +* Paramètres : gclass = classe GLib à initialiser. * +* pyclass = classe Python à initialiser. * * * * Description : Initialise la classe des greffons d'extension. * * * -* Retour : - * +* Retour : 0 pour indiquer un succès de l'opération. * * * * Remarques : - * * * ******************************************************************************/ -static void py_plugin_module_init_gclass(GPluginModuleClass *class, gpointer unused) +static int py_plugin_module_init_gclass(GPluginModuleClass *gclass, PyTypeObject *pyclass) { - class->init = NULL; - class->manage = py_plugin_module_manage_wrapper; - class->exit = py_plugin_module_exit; + PY_CLASS_SET_WRAPPER(gclass->get_filename, py_plugin_module_get_filename_wrapper); + PY_CLASS_SET_WRAPPER(gclass->get_modname, py_plugin_module_get_modname_wrapper); - class->plugins_loaded = py_plugin_module_notify_plugins_loaded_wrapper; + PY_CLASS_SET_WRAPPER(gclass->enable, py_plugin_module_enable_wrapper); + PY_CLASS_SET_WRAPPER(gclass->disable, py_plugin_module_disable_wrapper); - class->get_modname = py_plugin_module_get_modname_wrapper; - -#if 0 - -#ifdef INCLUDE_GTK_SUPPORT - class->include_theme = py_plugin_module_include_theme_wrapper; - class->notify_panel = py_plugin_module_notify_panel_creation_wrapper; - class->notify_docking = py_plugin_module_notify_panel_docking_wrapper; -#endif - - class->handle_content = py_plugin_module_handle_binary_content_wrapper; - class->handle_loaded = py_plugin_module_handle_loaded_content_wrapper; - - class->handle_fmt_analysis = py_plugin_module_handle_known_format_analysis_wrapper; - class->preload_format = py_plugin_module_preload_binary_format_wrapper; - class->attach_debug = py_plugin_module_attach_debug_format_wrapper; - - class->process_disass = py_plugin_module_process_disassembly_event_wrapper; - - class->detect = py_plugin_module_detect_external_tools_wrapper; - -#endif + return 0; } @@ -202,45 +147,56 @@ static void py_plugin_module_init_gclass(GPluginModuleClass *class, gpointer unu static int py_plugin_module_init(PyObject *self, PyObject *args, PyObject *kwds) { + const char *desc; /* Description plus loquace */ + const char *version; /* Version du greffon */ + const char *url; /* Site Web associé */ + const char *name; /* Désignation humaine courte */ + charp_array_t required; /* Liste des dépendances */ int ret; /* Bilan d'un appel */ GPluginModule *plugin; /* Greffon à manipuler */ - plugin_interface *iface; /* Interface à constituer */ - GPluginModule *dependency; /* Module nécessaire */ - PyObject *value; /* Valeur à présence imposée */ - size_t i; /* Boucle de parcours */ - PyObject *action; /* Identifiant d'une action */ + + static char *kwlist[] = { "name", "desc", "version", "url", "required", NULL }; #define PLUGIN_MODULE_DOC \ - "PythonModule is the class allowing the creation of Chrysalide plugins" \ - " for Python." \ + "PluginModule is the core class handling the Chrysalide plugins." \ + " Python plugins should inherit the pychrysalide.plugins.PythonPlugin" \ + " instead of this one." \ "\n" \ "Calls to the *__init__* constructor of this abstract object expect" \ - " no particular argument.\n" \ + " the following arguments as keyword parameters:\n" \ + "* *name*: a string providing a key name for the plugin;\n" \ + "* *desc* (optional): a string for a human readable description of the" \ + " features provided by the plugin;\n" \ + "* *version* (optional): a string providing the version of the plugin;" \ + " Version format is free;\n" \ + "* *url* (optional): a string for the homepage describing the plugin;\n"\ + "* *required* (optional): dependencies of the plugin." \ "\n" \ - "Several items have to be defined as class attributes in the final" \ - " class:\n" \ - "* *_name*: a string providing a small name for the plugin;\n" \ - "* *_desc*: a string for a human readable description of the plugin;\n" \ - "* *_version*: a string providing the version of the plugin;\n" \ - "* *_url*: a string for the homepage describing the plugin;\n" \ - "* *_actions*: a tuple of" \ - " pychrysalide.plugins.PluginModule.PluginAction defining the features" \ - " the plugin is bringing; this list can be empty.\n" \ + "Dependency to the *PyChrysalide* plugin is added automatically if not" \ + " specified.\n" \ "\n" \ - "Depending on the implemented actions, some of the following methods" \ - " have to be defined for new classes:\n" \ - "* pychrysalide.plugins.PluginModule._init_config();\n" \ - "* pychrysalide.plugins.PluginModule._notify_plugins_loaded();\n" \ - "* pychrysalide.plugins.PluginModule._include_theme();\n" \ - "* pychrysalide.plugins.PluginModule._on_panel_creation;\n" \ - "* pychrysalide.plugins.PluginModule._on_panel_docking();\n" \ - "* pychrysalide.plugins.PluginModule._handle_binary_content();\n" \ - "* pychrysalide.plugins.PluginModule._handle_loaded_content();\n" \ - "* pychrysalide.plugins.PluginModule._handle_format_analysis();\n" \ - "* pychrysalide.plugins.PluginModule._preload_format();\n" \ - "* pychrysalide.plugins.PluginModule._attach_debug_format();\n" \ - "* pychrysalide.plugins.PluginModule._process_disassembly_event();\n" \ - "* pychrysalide.plugins.PluginModule._detect_external_tools()." + "The following methods have to be defined for new classes:\n" \ + "* pychrysalide.plugins.PluginModule._get_filename();\n" \ + "* pychrysalide.plugins.PluginModule._get_modname();\n" \ + "* pychrysalide.plugins.PluginModule._enable();\n" \ + "* pychrysalide.plugins.PluginModule._disable()." + + /* Récupération des paramètres */ + + desc = NULL; + version = NULL; + url = NULL; + required.values = NULL; + required.length = 0; + + ret = PyArg_ParseTupleAndKeywords(args, kwds, "s|sssO&", kwlist, + &name, &desc, &version, &url, + convert_to_sequence_to_charp_array, &required); + if (!ret) return -1; + + required.values = realloc(required.values, ++required.length * sizeof(char *)); + + required.values[required.length - 1] = strdup("PyChrysalide"); /* Initialisation d'un objet GLib */ @@ -251,105 +207,17 @@ static int py_plugin_module_init(PyObject *self, PyObject *args, PyObject *kwds) plugin = G_PLUGIN_MODULE(pygobject_get(self)); - iface = calloc(1, sizeof(plugin_interface)); - plugin->interface = iface; - -#define LOAD_PYTHON_IFACE(attr) \ - do \ - { \ - value = PyObject_GetAttrString(self, "_" #attr); \ - if (value == NULL) \ - { \ - PyErr_SetString(PyExc_TypeError, _("A '_" #attr "' class attributes is missing.")); \ - return -1; \ - } \ - if (PyUnicode_Check(value)) \ - { \ - iface->attr = strdup(PyUnicode_AsUTF8(value)); \ - Py_DECREF(value); \ - } \ - else \ - { \ - Py_DECREF(value); \ - PyErr_SetString(PyExc_TypeError, _("The '_" #attr "' class attributes must be a string.")); \ - return -1; \ - } \ - assert(iface->attr != NULL); \ - } \ - while (0); - - LOAD_PYTHON_IFACE(name); - LOAD_PYTHON_IFACE(desc); - LOAD_PYTHON_IFACE(version); - LOAD_PYTHON_IFACE(url); - - iface->container = false; - - /** - * Comme le greffon n'est pas passé par la résolution des dépendances, - * orchestrée par la fonction g_plugin_module_resolve_dependencies(), - * on simule l'effet attendu en obtenant une référence par un appel à - * get_plugin_by_name(). - * - * L'incrémentation des références doit coller au plus près de - * l'inscription nominative du greffon : en cas de sortie impromptue - * (lorsqu'une erreur intervient pendant un chargement par exemple), - * l'état de l'ensemble est ainsi cohérent au moment du retrait du - * greffon fautif via la fonction g_plugin_module_dispose(). - */ - - lock_plugin_list_for_reading(); - dependency = get_plugin_by_name("PyChrysalide", NULL); - unlock_plugin_list_for_reading(); - - assert(dependency != NULL); - - if (dependency == NULL) - { - PyErr_SetString(PyExc_TypeError, _("The internal name of the Python plugin has changed!")); - return -1; - } - - iface->required = malloc(sizeof(char *)); - iface->required[0] = "PyChrysalide"; - iface->required_count = 1; + STORE_PLUGIN_ABI(plugin); - /* Validation du reste de l'interface */ - - value = PyObject_GetAttrString(self, "_actions"); - - if (value == NULL) + if (!g_plugin_module_create(plugin, name, desc, version, url, + CONST_ARRAY_CAST(required.values, char), required.length)) { - PyErr_SetString(PyExc_TypeError, _("An '_actions' class attributes is missing.")); + clean_charp_array(&required); + PyErr_SetString(PyExc_ValueError, _("Unable to create plugin module.")); return -1; } - if (!PyTuple_Check(value)) - { - Py_DECREF(value); - PyErr_SetString(PyExc_TypeError, _("The '_actions' class attributes must be a tuple.")); - return -1; - } - - iface->actions_count = PyTuple_Size(value); - iface->actions = malloc(iface->actions_count * sizeof(plugin_action_t)); - - for (i = 0; i < iface->actions_count; i++) - { - action = PyTuple_GetItem(value, i); - - if (!PyLong_Check(action)) - { - Py_DECREF(value); - PyErr_SetString(PyExc_TypeError, _("invalid type for plugin action.")); - return -1; - } - - iface->actions[i] = PyLong_AsUnsignedLong(action); - - } - - Py_DECREF(value); + clean_charp_array(&required); return 0; @@ -358,56 +226,55 @@ static int py_plugin_module_init(PyObject *self, PyObject *args, PyObject *kwds) /****************************************************************************** * * -* Paramètres : plugin = greffon à manipuler. * +* Paramètres : plugin = greffon à consulter. * * * -* Description : Encadre une étape de la vie d'un greffon. * +* Description : Pointe le fichier contenant le greffon manipulé. * * * -* Retour : Bilan de l'opération. * +* Retour : Chemin d'accès au greffon. * * * * Remarques : - * * * ******************************************************************************/ -static bool py_plugin_module_manage_wrapper(GPluginModule *plugin) +static char *py_plugin_module_get_filename_wrapper(const GPluginModule *plugin) { - bool result; /* Bilan à faire remonter */ + char *result; /* Désignation brute à renvoyer*/ PyGILState_STATE gstate; /* Sauvegarde d'environnement */ PyObject *pyobj; /* Objet Python concerné */ - PyObject *args; /* Arguments pour l'appel */ PyObject *pyret; /* Bilan d'exécution */ -#define PLUGIN_MODULE_MANAGE_WRAPPER PYTHON_WRAPPER_DEF \ -( \ - _manage, "$self, action, /", \ - METH_VARARGS, \ - "Abstract method called to react to several steps of the plugin" \ - " life.\n" \ - "\n" \ - "The expected action is a" \ - " pychrysalide.plugins.PluginModule.PluginAction value.\n" \ - "\n" \ - "This method has to be defined in order to handle actions such as" \ - " *PLUGIN_LOADED*." \ +#define PLUGIN_MODULE_GET_FILENAME_WRAPPER PYTHON_WRAPPER_DEF \ +( \ + _get_filename, "$self, /", \ + METH_NOARGS, \ + "Abstract method providing the filename of the script.\n" \ + "\n" \ + "The result should be the string value pointing to the plugin" \ + " file path.\n" \ ) - result = true; + result = NULL; gstate = PyGILState_Ensure(); pyobj = pygobject_new(G_OBJECT(plugin)); - if (has_python_method(pyobj, "_manage")) + if (has_python_method(pyobj, "_get_filename")) { - args = PyTuple_New(1); + pyret = run_python_method(pyobj, "_get_filename", NULL); - PyTuple_SetItem(args, 0, PyLong_FromUnsignedLong(PGA_PLUGIN_LOADED)); + if (pyret != NULL) + { + if (!PyUnicode_Check(pyret)) + g_plugin_module_log_variadic_message(plugin, LMT_ERROR, + _("The returned raw name must be a string")); - pyret = run_python_method(pyobj, "_manage", args); + else + result = strdup(PyUnicode_DATA(pyret)); - result = (pyret == Py_True); + } Py_XDECREF(pyret); - Py_DECREF(args); } @@ -422,44 +289,62 @@ static bool py_plugin_module_manage_wrapper(GPluginModule *plugin) /****************************************************************************** * * -* Paramètres : plugin = greffon à manipuler. * +* Paramètres : plugin = greffon à valider. * * * -* Description : Assiste la désactivation d'un greffon. * +* Description : Fournit le nom brut associé au greffon. * * * -* Retour : Bilan de l'opération. * +* Retour : Désignation brute du greffon. * * * * Remarques : - * * * ******************************************************************************/ -static bool py_plugin_module_exit(GPluginModule *plugin) +static char *py_plugin_module_get_modname_wrapper(const GPluginModule *plugin) { - bool result; /* Bilan à faire remonter */ - plugin_interface *final; /* Interface finale conservée */ + char *result; /* Désignation brute à renvoyer*/ + PyGILState_STATE gstate; /* Sauvegarde d'environnement */ + PyObject *pyobj; /* Objet Python concerné */ + PyObject *pyret; /* Bilan d'exécution */ - result = true; +#define PLUGIN_MODULE_GET_MODNAME_WRAPPER PYTHON_WRAPPER_DEF \ +( \ + _get_modname, "$self, /", \ + METH_NOARGS, \ + "Abstract method providing the raw module name of the loaded" \ + " plugin.\n" \ + "\n" \ + "The result should be a short string value.\n" \ +) - final = (plugin_interface *)G_PLUGIN_MODULE(plugin)->interface; + result = NULL; + + gstate = PyGILState_Ensure(); - if (final != NULL) + pyobj = pygobject_new(G_OBJECT(plugin)); + + if (has_python_method(pyobj, "_get_modname")) { - if (final->name != NULL) free(final->name); - if (final->desc != NULL) free(final->desc); - if (final->version != NULL) free(final->version); - if (final->url != NULL) free(final->url); + pyret = run_python_method(pyobj, "_get_modname", NULL); - assert(final->required_count == 1); + if (pyret != NULL) + { + if (!PyUnicode_Check(pyret)) + g_plugin_module_log_variadic_message(plugin, LMT_ERROR, + _("The returned raw name must be a string")); - if (final->required != NULL) - free(final->required); + else + result = strdup(PyUnicode_DATA(pyret)); - if (final->actions != NULL) - free(final->actions); + } - free(final); + Py_XDECREF(pyret); } + Py_DECREF(pyobj); + + PyGILState_Release(gstate); + return result; } @@ -468,51 +353,45 @@ static bool py_plugin_module_exit(GPluginModule *plugin) /****************************************************************************** * * * Paramètres : plugin = greffon à manipuler. * -* action = type d'action attendue. * * * -* Description : Accompagne la fin du chargement des modules natifs. * +* Description : Prend acte de l'activation du greffon. * * * -* Retour : - * +* Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ -static void py_plugin_module_notify_plugins_loaded_wrapper(GPluginModule *plugin, PluginAction action) +static bool py_plugin_module_enable_wrapper(GPluginModule *plugin) { + bool result; /* Bilan à retourner */ PyGILState_STATE gstate; /* Sauvegarde d'environnement */ PyObject *pyobj; /* Objet Python concerné */ - PyObject *args; /* Arguments pour l'appel */ PyObject *pyret; /* Bilan d'exécution */ -#define PLUGIN_MODULE_NOTIFY_PLUGINS_LOADED_WRAPPER PYTHON_WRAPPER_DEF \ -( \ - _notify_plugins_loaded, "$self, action, /", \ - METH_VARARGS, \ - "Abstract method called once all the (native?) plugins are" \ - " loaded.\n" \ - "\n" \ - "The expected action is a" \ - " pychrysalide.plugins.PluginModule.PluginAction value.\n" \ - "\n" \ - "This method has to be defined in order to handle actions such as" \ - " *NATIVE_PLUGINS_LOADED* or *PLUGINS_LOADED*." \ +#define PLUGIN_MODULE_ENABLE_WRAPPER PYTHON_WRAPPER_DEF \ +( \ + _enable, "$self, /", \ + METH_VARARGS, \ + "Abstract method called when the plugin gets enabled.\n" \ + "\n" \ + "The result is a boolean status: *False* if an implementation" \ + " failed, *True* otherwise." \ ) + result = true; + gstate = PyGILState_Ensure(); pyobj = pygobject_new(G_OBJECT(plugin)); - if (has_python_method(pyobj, "_notify_plugins_loaded")) + if (has_python_implementation_method(pyobj, "_enable")) { - args = PyTuple_New(1); - - PyTuple_SetItem(args, 0, PyLong_FromUnsignedLong(action)); + pyret = run_python_method(pyobj, "_enable", NULL); - pyret = run_python_method(pyobj, "_notify_plugins_loaded", args); + result = (pyret == Py_True); Py_XDECREF(pyret); - Py_DECREF(args); } @@ -520,36 +399,6 @@ static void py_plugin_module_notify_plugins_loaded_wrapper(GPluginModule *plugin PyGILState_Release(gstate); -} - - -/****************************************************************************** -* * -* Paramètres : self = objet Python concerné par l'appel. * -* args = arguments fournis à l'appel. * -* * -* Description : Fournit le nom brut associé au greffon par défaut. * -* * -* Retour : Désignation brute du greffon. * -* * -* Remarques : - * -* * -******************************************************************************/ - -static PyObject *py_plugin_module_get_modname_by_default(PyObject *self, PyObject *args) -{ - PyObject *result; /* Bilan à retourner */ - GPluginModule *plugin; /* Version native du greffon */ - char *path; /* Chemin à traiter */ - - plugin = G_PLUGIN_MODULE(pygobject_get(self)); - - path = strdup(g_plugin_module_get_filename(plugin)); - - result = PyUnicode_FromString(basename(path)); - - free(path); - return result; } @@ -557,52 +406,44 @@ static PyObject *py_plugin_module_get_modname_by_default(PyObject *self, PyObjec /****************************************************************************** * * -* Paramètres : plugin = greffon à valider. * +* Paramètres : plugin = greffon à manipuler. * * * -* Description : Fournit le nom brut associé au greffon. * +* Description : Prend acte de la désactivation du greffon. * * * -* Retour : Désignation brute du greffon. * +* Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ -static char *py_plugin_module_get_modname_wrapper(const GPluginModule *plugin) +static bool py_plugin_module_disable_wrapper(GPluginModule *plugin) { - char *result; /* Désignation brute à renvoyer*/ + bool result; /* Bilan à retourner */ PyGILState_STATE gstate; /* Sauvegarde d'environnement */ PyObject *pyobj; /* Objet Python concerné */ PyObject *pyret; /* Bilan d'exécution */ -#define PLUGIN_MODULE_GET_MODNAME_WRAPPER PYTHON_WRAPPER_DEF_WITH \ -( \ - _get_modname, "$self, /", \ - METH_VARARGS, py_plugin_module_get_modname_by_default, \ - "(Abstract) method providing the raw module name of the plugin.\n" \ - " loaded.\n" \ - "\n" \ - "The result should be a short string value.\n" \ - "\n" \ - "A default implementation builds the module name from the Python" \ - " script filename." \ +#define PLUGIN_MODULE_DISABLE_WRAPPER PYTHON_WRAPPER_DEF \ +( \ + _disable, "$self, /", \ + METH_VARARGS, \ + "Abstract method called when the plugin gets disabled.\n" \ + "\n" \ + "The result is a boolean status: *False* if an implementation" \ + " failed, *True* otherwise." \ ) - result = NULL; + result = true; gstate = PyGILState_Ensure(); pyobj = pygobject_new(G_OBJECT(plugin)); - if (has_python_method(pyobj, "_get_modname")) + if (has_python_implementation_method(pyobj, "_disable")) { - pyret = run_python_method(pyobj, "_get_modname", NULL); + pyret = run_python_method(pyobj, "_disable", NULL); - if (!PyUnicode_Check(pyret)) - g_plugin_module_log_variadic_message(plugin, LMT_ERROR, - _("The returned raw name must be a string")); - - else - result = strdup(PyUnicode_DATA(pyret)); + result = (pyret == Py_True); Py_XDECREF(pyret); @@ -617,6 +458,15 @@ static char *py_plugin_module_get_modname_wrapper(const GPluginModule *plugin) } + + + + + + + + + #if 0 #ifdef INCLUDE_GTK_SUPPORT @@ -1382,51 +1232,42 @@ static void py_plugin_module_detect_external_tools_wrapper(const GPluginModule * * Paramètres : self = objet Python concerné par l'appel. * * args = arguments fournis à l'appel. * * * -* Description : Construit le nom d'un fichier de configuration du greffon. * +* Description : Affiche un message dans le journal des messages système. * * * -* Retour : Chemin d'accès déterminé, ou NULL en cas d'erreur. * +* Retour : Rien en équivalent Python. * * * * Remarques : - * * * ******************************************************************************/ -static PyObject *py_plugin_module_build_config_filename(PyObject *self, PyObject *args) +static PyObject *py_plugin_module_log_message(PyObject *self, PyObject *args) { PyObject *result; /* Bilan à retourner */ - const char *final; /* Suffixe de fichier imposé */ - int create; /* Volonté de création */ - char *filename; /* Nom de fichier déterminé */ + LogMessageType type; /* Espèce du message */ + const char *msg; /* Contenu du message */ -#define PLUGIN_MODULE_BUILD_CONFIG_FILENAME_METHOD PYTHON_METHOD_DEF \ +#define PLUGIN_MODULE_LOG_MESSAGE_METHOD PYTHON_METHOD_DEF \ ( \ - build_config_filename, "final, /, create=False", \ + log_message, "type, msg, /", \ METH_VARARGS, py_plugin_module, \ - "Build a filename suitable for the plugin configuration, ending with" \ - " the *final* suffix.\n" \ + "Display a message in the log window, in graphical mode, or in the" \ + " console output if none.\n" \ "\n" \ - "If the *create* parameter is set, the path to this filename is" \ - " created.\n" \ + "The type of the message has to be a pychrysalide.core.LogMessageType" \ + " value." \ "\n" \ - "The result is a string or None on failure." \ + "The only difference with the main pychrysalide.core.log_message()" \ + " function is that messages are automatically prefixed with the plugin" \ + " name here." \ ) - create = 0; - - if (!PyArg_ParseTuple(args, "s|p", &final, &create)) + if (!PyArg_ParseTuple(args, "O&s", convert_to_log_message_type, &type, &msg)) return NULL; - filename = g_plugin_module_build_config_filename(G_PLUGIN_MODULE(pygobject_get(self)), final, create); + g_plugin_module_log_simple_message(G_PLUGIN_MODULE(pygobject_get(self)), type, msg); - if (filename != NULL) - { - result = PyUnicode_FromString(filename); - free(filename); - } - else - { - result = Py_None; - Py_INCREF(result); - } + result = Py_None; + Py_INCREF(result); return result; @@ -1435,45 +1276,35 @@ static PyObject *py_plugin_module_build_config_filename(PyObject *self, PyObject /****************************************************************************** * * -* Paramètres : self = objet Python concerné par l'appel. * -* args = arguments fournis à l'appel. * +* Paramètres : self = objet Python concerné par l'appel. * +* closure = non utilisé ici. * * * -* Description : Affiche un message dans le journal des messages système. * +* Description : Indique le nom associé à un greffon. * * * -* Retour : Rien en équivalent Python. * +* Retour : Désignation interne de l'extension, pour référence(s). * * * * Remarques : - * * * ******************************************************************************/ -static PyObject *py_plugin_module_log_message(PyObject *self, PyObject *args) +static PyObject *py_plugin_module_get_name(PyObject *self, void *closure) { - PyObject *result; /* Bilan à retourner */ - LogMessageType type; /* Espèce du message */ - const char *msg; /* Contenu du message */ + PyObject *result; /* Valeur à retourner */ + GPluginModule *plugin; /* Version native du greffon */ + const char *name; /* Nom attribué au greffon */ -#define PLUGIN_MODULE_LOG_MESSAGE_METHOD PYTHON_METHOD_DEF \ -( \ - log_message, "type, msg, /", \ - METH_VARARGS, py_plugin_module, \ - "Display a message in the log window, in graphical mode, or in the" \ - " console output if none.\n" \ - "\n" \ - "The type of the message has to be a pychrysalide.core.LogMessageType" \ - " value." \ - "\n" \ - "The only difference with the main pychrysalide.core.log_message()" \ - " function is that messages are automatically prefixed with the plugin" \ - " name here." \ +#define PLUGIN_MODULE_NAME_ATTRIB PYTHON_GET_DEF_FULL \ +( \ + name, py_plugin_module, \ + "Name of the plugin. This string value is used to" \ + " reference the plugin as dependency." \ ) - if (!PyArg_ParseTuple(args, "O&s", convert_to_log_message_type, &type, &msg)) - return NULL; + plugin = G_PLUGIN_MODULE(pygobject_get(self)); - g_plugin_module_log_simple_message(G_PLUGIN_MODULE(pygobject_get(self)), type, msg); + name = g_plugin_module_get_name(plugin); - result = Py_None; - Py_INCREF(result); + result = PyUnicode_FromString(name); return result; @@ -1485,32 +1316,31 @@ static PyObject *py_plugin_module_log_message(PyObject *self, PyObject *args) * Paramètres : self = objet Python concerné par l'appel. * * closure = non utilisé ici. * * * -* Description : Fournit le nom brut associé au greffon. * +* Description : Fournit une description fonctionnelle d'un greffon. * * * -* Retour : Désignation brute du greffon. * +* Retour : Description textuelle associée à une extension ou NULL. * * * * Remarques : - * * * ******************************************************************************/ -static PyObject *py_plugin_module_get_modname(PyObject *self, void *closure) +static PyObject *py_plugin_module_get_desc(PyObject *self, void *closure) { PyObject *result; /* Valeur à retourner */ GPluginModule *plugin; /* Version native du greffon */ - char *modname; /* Désignation brute */ + const char *desc; /* Description du greffon */ -#define PLUGIN_MODULE_MODNAME_ATTRIB PYTHON_GET_DEF_FULL \ -( \ - modname, py_plugin_module, \ - "Raw module name of the plugin." \ +#define PLUGIN_MODULE_DESC_ATTRIB PYTHON_GET_DEF_FULL \ +( \ + desc, py_plugin_module, \ + "Optional plugin description as string or *None*." \ ) plugin = G_PLUGIN_MODULE(pygobject_get(self)); - modname = g_plugin_module_get_modname(plugin); - result = PyUnicode_FromString(modname); + desc = g_plugin_module_get_desc(plugin); - free(modname); + result = PyUnicode_FromString(desc); return result; @@ -1522,30 +1352,32 @@ static PyObject *py_plugin_module_get_modname(PyObject *self, void *closure) * Paramètres : self = objet Python concerné par l'appel. * * closure = non utilisé ici. * * * -* Description : Indique le fichier contenant le greffon manipulé. * +* Description : Fournit la version d'un greffon et de ses fonctionnalités. * * * -* Retour : Chemin d'accès au greffon. * +* Retour : Version sous forme de chaîne de caractères ou None. * * * * Remarques : - * * * ******************************************************************************/ -static PyObject *py_plugin_module_get_filename(PyObject *self, void *closure) +static PyObject *py_plugin_module_get_version(PyObject *self, void *closure) { PyObject *result; /* Valeur à retourner */ GPluginModule *plugin; /* Version native du greffon */ - const char *filename; /* Chemin d'accès associé */ + const char *version; /* Version du greffon */ -#define PLUGIN_MODULE_FILENAME_ATTRIB PYTHON_GET_DEF_FULL \ +#define PLUGIN_MODULE_VERSION_ATTRIB PYTHON_GET_DEF_FULL \ ( \ - filename, py_plugin_module, \ - "Filename of the plugin." \ + version, py_plugin_module, \ + "Optional plugin version, in free format, as string," \ + " or *None*." \ ) plugin = G_PLUGIN_MODULE(pygobject_get(self)); - filename = g_plugin_module_get_filename(plugin); - result = PyUnicode_FromString(filename); + version = g_plugin_module_get_version(plugin); + + result = PyUnicode_FromString(version); return result; @@ -1557,113 +1389,204 @@ static PyObject *py_plugin_module_get_filename(PyObject *self, void *closure) * Paramètres : self = objet Python concerné par l'appel. * * closure = non utilisé ici. * * * -* Description : Fournit la description du greffon dans son intégralité. * +* Description : Fournit l'URL des ressources en ligne liées à un greffon. * * * -* Retour : Interfaçage renseigné. * +* Retour : URL de renvoi associée à une extension ou None. * * * * Remarques : - * * * ******************************************************************************/ -static PyObject *py_plugin_module_get_interface(PyObject *self, void *closure) +static PyObject *py_plugin_module_get_url(PyObject *self, void *closure) { PyObject *result; /* Valeur à retourner */ GPluginModule *plugin; /* Version native du greffon */ - const plugin_interface *iface; /* Interface liée à traduire */ + const char *url; /* URL associée au greffon */ -#define PLUGIN_MODULE_INTERFACE_ATTRIB PYTHON_GET_DEF_FULL \ +#define PLUGIN_MODULE_URL_ATTRIB PYTHON_GET_DEF_FULL \ ( \ - interface, py_plugin_module, \ - "Interface exported by the plugin..\n" \ - "\n" \ - "This property is a pychrysalide.StructObject instance." \ - "\n" \ - "The provided information is composed of the following" \ - " properties :\n" \ - "\n" \ - "* gtp_name;\n" \ - "* name;\n" \ - "* desc;\n" \ - "* version;\n" \ - "* url;\n" \ - "* container;\n" \ - "* required;\n" \ - "* actions.\n" \ - "\n" \ - "The *gtp_name* value may be *None* for non-native plugin." \ - " All other fields carry a string value except:\n" \ - "* *container*: a boolean status indicating if the plugin" \ - " can embed other plugins;\n" \ - "* *required*: a tuple of depedencies names;\n" \ - "* *actions*: a tuple of available features from the plugin"\ - " coded as pychrysalide.plugins.PluginModule.PluginAction" \ - " values." \ + url, py_plugin_module, \ + "Optional URL pointing to the plugin homepage as string" \ + " or *None*." \ ) plugin = G_PLUGIN_MODULE(pygobject_get(self)); - iface = g_plugin_module_get_interface(plugin); - result = translate_plugin_interface_to_python(iface); + url = g_plugin_module_get_url(plugin); + + result = PyUnicode_FromString(url); return result; } -#if 0 + /****************************************************************************** * * * Paramètres : self = objet Python concerné par l'appel. * * closure = non utilisé ici. * * * -* Description : Fournit la configuration mise en place pour le greffon. * +* Description : Fournit la liste des dépendances d'un greffon donné. * * * -* Retour : Configuration dédiée à l'extension. * +* Retour : Liste des noms d'extensions requises pour une extension. * * * * Remarques : - * * * ******************************************************************************/ -static PyObject *py_plugin_module_get_config(PyObject *self, void *closure) +static PyObject *py_plugin_module_get_requirements(PyObject *self, void *closure) { PyObject *result; /* Valeur à retourner */ GPluginModule *plugin; /* Version native du greffon */ - GGenConfig *config; /* Configuration associée */ + const char * const *required; /* Liste de dépendances */ + size_t count; /* Nombre de ces dépendances */ + size_t i; /* Boucle de parcours */ -#define PLUGIN_MODULE_CONFIG_ATTRIB PYTHON_GET_DEF_FULL \ +#define PLUGIN_MODULE_REQUIREMENTS_ATTRIB PYTHON_GET_DEF_FULL \ ( \ - config, py_plugin_module, \ - "Dedicated configuration for the plugin." \ - "\n" \ - "The value is a pychrysalide.glibext.GenConfig instance" \ - " or None if the configuration is not yet created.\n" \ - "\n" \ - "As configuration storage path depends on the plugin name," \ - " all plugin properties have to get fully loaded by the" \ - " core before the configuration can be setup." \ - "automatically" \ + requirements, py_plugin_module, \ + "Tuple of the plugin dependencies." \ +) + + plugin = G_PLUGIN_MODULE(pygobject_get(self)); + + required = g_plugin_module_get_requirements(plugin, &count); + + result = PyTuple_New(count); + + for (i = 0; i < count; i++) + PyTuple_SetItem(result, i, PyUnicode_FromString(required[i])); + + return result; + +} + + + + + + + +/****************************************************************************** +* * +* Paramètres : self = objet Python concerné par l'appel. * +* closure = non utilisé ici. * +* * +* Description : Indique le fichier contenant le greffon manipulé. * +* * +* Retour : Chemin d'accès au greffon. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_plugin_module_get_filename(PyObject *self, void *closure) +{ + PyObject *result; /* Valeur à retourner */ + GPluginModule *plugin; /* Version native du greffon */ + char *filename; /* Chemin d'accès associé */ + +#define PLUGIN_MODULE_FILENAME_ATTRIB PYTHON_GET_DEF_FULL \ +( \ + filename, py_plugin_module, \ + "Filename of the plugin." \ +) + + plugin = G_PLUGIN_MODULE(pygobject_get(self)); + + filename = g_plugin_module_get_filename(plugin); + + if (filename != NULL) + { + result = PyUnicode_FromString(filename); + + free(filename); + + } + + else + { + /** + * La méthode de classe sollicitée a renvoyé une valeur nulle. + * + * Si cette méthode correspond à une implémentation Python + * (avec un appel à not_yet_implemented_method()), une exception + * est déjà en place. + * + * Si aucune exception n'a été prévue, un rattrapage est effectué ici. + */ + + if (PyErr_Occurred() == NULL) + PyErr_SetString(PyExc_NotImplementedError, _("unexpected NULL value as filename")); + + result = NULL; + + } + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : self = objet Python concerné par l'appel. * +* closure = non utilisé ici. * +* * +* Description : Fournit le nom brut associé au greffon. * +* * +* Retour : Désignation brute du greffon. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_plugin_module_get_modname(PyObject *self, void *closure) +{ + PyObject *result; /* Valeur à retourner */ + GPluginModule *plugin; /* Version native du greffon */ + char *modname; /* Désignation brute */ + +#define PLUGIN_MODULE_MODNAME_ATTRIB PYTHON_GET_DEF_FULL \ +( \ + modname, py_plugin_module, \ + "Raw module name of the plugin." \ ) plugin = G_PLUGIN_MODULE(pygobject_get(self)); - config = g_plugin_module_get_config(plugin); - if (config == NULL) + modname = g_plugin_module_get_modname(plugin); + + if (modname != NULL) { - result = Py_None; - Py_INCREF(result); + result = PyUnicode_FromString(modname); + + free(modname); + } else { - result = pygobject_new(G_OBJECT(config)); + /** + * La méthode de classe sollicitée a renvoyé une valeur nulle. + * + * Si cette méthode correspond à une implémentation Python + * (avec un appel à not_yet_implemented_method()), une exception + * est déjà en place. + * + * Si aucune exception n'a été prévue, un rattrapage est effectué ici. + */ + + if (PyErr_Occurred() == NULL) + PyErr_SetString(PyExc_NotImplementedError, _("unexpected NULL value as modname")); - g_object_unref(G_OBJECT(config)); + result = NULL; } return result; } -#endif /****************************************************************************** @@ -1681,33 +1604,22 @@ static PyObject *py_plugin_module_get_config(PyObject *self, void *closure) PyTypeObject *get_python_plugin_module_type(void) { static PyMethodDef py_plugin_module_methods[] = { - PLUGIN_MODULE_MANAGE_WRAPPER, - PLUGIN_MODULE_NOTIFY_PLUGINS_LOADED_WRAPPER, + PLUGIN_MODULE_GET_FILENAME_WRAPPER, PLUGIN_MODULE_GET_MODNAME_WRAPPER, -#if 0 -#ifdef INCLUDE_GTK_SUPPORT - PLUGIN_MODULE_INCLUDE_THEME_WRAPPER, - PLUGIN_MODULE_ON_PANEL_CREATION_WRAPPER, - PLUGIN_MODULE_ON_PANEL_DOCKING_WRAPPER, -#endif - PLUGIN_MODULE_HANDLE_BINARY_CONTENT_WRAPPER, - PLUGIN_MODULE_HANDLE_LOADED_CONTENT_WRAPPER, - PLUGIN_MODULE_HANDLE_BINARY_FORMAT_ANALYSIS_WRAPPER, - PLUGIN_MODULE_PRELOAD_BINARY_FORMAT_WRAPPER, - PLUGIN_MODULE_ATTACH_DEBUG_FORMAT_WRAPPER, - PLUGIN_MODULE_PROCESS_DISASSEMBLY_EVENT_WRAPPER, - PLUGIN_MODULE_DETECT_EXTERNAL_TOOLS_WRAPPER, - PLUGIN_MODULE_BUILD_CONFIG_FILENAME_METHOD, -#endif + PLUGIN_MODULE_ENABLE_WRAPPER, + PLUGIN_MODULE_DISABLE_WRAPPER, PLUGIN_MODULE_LOG_MESSAGE_METHOD, { NULL } }; static PyGetSetDef py_plugin_module_getseters[] = { - PLUGIN_MODULE_MODNAME_ATTRIB, + PLUGIN_MODULE_NAME_ATTRIB, + PLUGIN_MODULE_DESC_ATTRIB, + PLUGIN_MODULE_VERSION_ATTRIB, + PLUGIN_MODULE_URL_ATTRIB, + PLUGIN_MODULE_REQUIREMENTS_ATTRIB, PLUGIN_MODULE_FILENAME_ATTRIB, - PLUGIN_MODULE_INTERFACE_ATTRIB, - //PLUGIN_MODULE_CONFIG_ATTRIB, + PLUGIN_MODULE_MODNAME_ATTRIB, { NULL } }; @@ -1761,10 +1673,9 @@ bool ensure_python_plugin_module_is_registered(void) dict = PyModule_GetDict(module); - if (!register_class_for_pygobject(dict, G_TYPE_PLUGIN_MODULE, type)) - return false; + pyg_register_class_init(G_TYPE_PLUGIN_MODULE, (PyGClassInitFunc)py_plugin_module_init_gclass); - if (!define_plugin_module_constants(type)) + if (!register_class_for_pygobject(dict, G_TYPE_PLUGIN_MODULE, type)) return false; } @@ -1772,80 +1683,3 @@ bool ensure_python_plugin_module_is_registered(void) return true; } - - -/****************************************************************************** -* * -* 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 : - * -* * -******************************************************************************/ - -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)); - - result->filename = strdup(filename); - - /** - * L'instance Python et l'objet GLib résultante 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; - -} diff --git a/plugins/pychrysalide/plugins/plugin.h b/plugins/pychrysalide/plugins/plugin.h index ad54b8e..fd3d239 100644 --- a/plugins/pychrysalide/plugins/plugin.h +++ b/plugins/pychrysalide/plugins/plugin.h @@ -27,13 +27,9 @@ #include <Python.h> -#include <glib-object.h> #include <stdbool.h> -#include <plugins/plugin.h> - - /* Fournit un accès à une définition de type à diffuser. */ PyTypeObject *get_python_plugin_module_type(void); @@ -41,9 +37,6 @@ PyTypeObject *get_python_plugin_module_type(void); /* Prend en charge l'objet 'pychrysalide.plugins.PluginModule'. */ bool ensure_python_plugin_module_is_registered(void); -/* Crée un greffon à partir de code Python. */ -GPluginModule *create_python_plugin(const char *, const char *); - #endif /* _PLUGINS_PYCHRYSALIDE_PLUGINS_PLUGIN_H */ diff --git a/plugins/pychrysalide/plugins/python-int.h b/plugins/pychrysalide/plugins/python-int.h new file mode 100644 index 0000000..7408d18 --- /dev/null +++ b/plugins/pychrysalide/plugins/python-int.h @@ -0,0 +1,58 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * python-int.h - prototypes internes pour la déclinaison Python de greffons + * + * Copyright (C) 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 <http://www.gnu.org/licenses/>. + */ + + +#ifndef _PLUGINS_PYCHRYSALIDE_PLUGINS_PYTHON_INT_H +#define _PLUGINS_PYCHRYSALIDE_PLUGINS_PYTHON_INT_H + + +#include "python.h" + + +#include <plugins/plugin-int.h> + + + +/* Greffon Python pour Chrysalide (instance) */ +struct _GPythonPlugin +{ + GPluginModule parent; /* A laisser en premier */ + + char *file; /* Valeur initiale de __file__ */ + +}; + + +/* Greffon Python pour Chrysalide (classe) */ +struct _GPythonPluginClass +{ + GPluginModuleClass parent; /* A laisser en premier */ + +}; + + +/* Met en place un greffon Python. */ +bool g_python_plugin_create(GPythonPlugin *, const char *, const char *, const char *, const char *, char ***, size_t *, const char *); + + + +#endif /* _PLUGINS_PYCHRYSALIDE_PLUGINS_PYTHON_INT_H */ diff --git a/plugins/pychrysalide/plugins/python.c b/plugins/pychrysalide/plugins/python.c new file mode 100644 index 0000000..a958a8d --- /dev/null +++ b/plugins/pychrysalide/plugins/python.c @@ -0,0 +1,489 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * python.c - déclinaison Python de greffons + * + * Copyright (C) 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "python.h" + + +#include <malloc.h> +#include <pygobject.h> + + +#include <common/compiler.h> +#include <common/cpp.h> + + +#include "plugin.h" +#include "python-int.h" +#include "../access.h" +#include "../helpers.h" + + + +/* ------------------------- COMPOSITION DE NOUVEAU GREFFON ------------------------- */ + + +/* Initialise la classe des greffons Python. */ +static void g_python_plugin_class_init(GPythonPluginClass *); + +/* Initialise une instance de greffon Python. */ +static void g_python_plugin_init(GPythonPlugin *); + +/* Supprime toutes les références externes. */ +static void g_python_plugin_dispose(GPythonPlugin *); + +/* Procède à la libération totale de la mémoire. */ +static void g_python_plugin_finalize(GPythonPlugin *); + + + +/* --------------------- IMPLEMENTATION DES FONCTIONS DE CLASSE --------------------- */ + + +/* Pointe le fichier contenant le greffon manipulé. */ +static char *g_python_plugin_get_filename(const GPythonPlugin *); + +/* Fournit le nom brut associé au greffon. */ +static char *g_python_plugin_get_modname(const GPythonPlugin *); + + + +/* ------------------------ GLUE POUR CREATION DEPUIS PYTHON ------------------------ */ + + +CREATE_DYN_ABSTRACT_CONSTRUCTOR(python_plugin, G_TYPE_PYTHON_PLUGIN, NULL); + +/* Initialise une instance sur la base du dérivé de GObject. */ +static int py_python_plugin_init(PyObject *self, PyObject *args, PyObject *kwds); + + + +/* ---------------------------------------------------------------------------------- */ +/* COMPOSITION DE NOUVEAU GREFFON */ +/* ---------------------------------------------------------------------------------- */ + + +/* Indique le type défini pour un greffon Python. */ +G_DEFINE_TYPE(GPythonPlugin, g_python_plugin, G_TYPE_PLUGIN_MODULE); + + + +/****************************************************************************** +* * +* Paramètres : class = classe à initialiser. * +* * +* Description : Initialise la classe des greffons Python. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_python_plugin_class_init(GPythonPluginClass *class) +{ + GObjectClass *object; /* Autre version de la classe */ + GPluginModuleClass *plugin; /* Version parente de la classe*/ + + object = G_OBJECT_CLASS(class); + + object->dispose = (GObjectFinalizeFunc/* ! */)g_python_plugin_dispose; + object->finalize = (GObjectFinalizeFunc)g_python_plugin_finalize; + + plugin = G_PLUGIN_MODULE_CLASS(class); + + plugin->get_filename = (get_plugin_filename_fc)g_python_plugin_get_filename; + plugin->get_modname = (get_plugin_modname_fc)g_python_plugin_get_modname; + +} + + +/****************************************************************************** +* * +* Paramètres : plugin = instance à initialiser. * +* * +* Description : Initialise une instance de greffon Python. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_python_plugin_init(GPythonPlugin *plugin) +{ + STORE_PLUGIN_ABI(plugin); + + plugin->file = NULL; + +} + + +/****************************************************************************** +* * +* Paramètres : plugin = instance d'objet GLib à traiter. * +* * +* Description : Supprime toutes les références externes. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void g_python_plugin_dispose(GPythonPlugin *plugin) +{ + G_OBJECT_CLASS(g_python_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_python_plugin_finalize(GPythonPlugin *plugin) +{ + if (plugin->file != NULL) + free(plugin->file); + + G_OBJECT_CLASS(g_python_plugin_parent_class)->finalize(G_OBJECT(plugin)); + +} + + +/****************************************************************************** +* * +* Paramètres : plugin = instance à initialiser pleinement. * +* name = nom du greffon pour référence, principalement. * +* desc = présentation éventuelle à destination humaine. * +* version = indication de version éventuelle. * +* url = référence vers une ressource en ligne. * +* required = liste de dépendances éventuelles ou NULL. * +* count = taille de cette liste. * +* file = emplacement du script considéré. * +* * +* Description : Met en place un greffon Python. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : Le transfert de propriétée du module est total. * +* * +******************************************************************************/ + +bool g_python_plugin_create(GPythonPlugin *plugin, const char *name, const char *desc, const char *version, const char *url, char ***required, size_t *count, const char *file) +{ + bool result; /* Bilan à retourner */ + + /* Ajout imposé d'une dépendance à Python */ + + *required = realloc(*required, ++(*count) * sizeof(char *)); + + (*required)[*count - 1] = strdup("PyChrysalide"); + + /* Poursuite de la mise en place */ + + result = g_plugin_module_create(G_PLUGIN_MODULE(plugin), + name, desc, version, url, + CONST_ARRAY_CAST(*required, char), *count); + + if (result) + plugin->file = strdup(file); + + return result; + +} + + + +/* ---------------------------------------------------------------------------------- */ +/* IMPLEMENTATION DES FONCTIONS DE CLASSE */ +/* ---------------------------------------------------------------------------------- */ + + +/****************************************************************************** +* * +* Paramètres : plugin = greffon à consulter. * +* * +* Description : Pointe le fichier contenant le greffon manipulé. * +* * +* Retour : Chemin d'accès au greffon. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static char *g_python_plugin_get_filename(const GPythonPlugin *plugin) +{ + char *result; /* Chemin d'accès à renvoyer */ + + result = strdup(plugin->file); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : plugin = greffon à valider. * +* * +* Description : Fournit le nom brut associé au greffon. * +* * +* Retour : Désignation brute du greffon. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static char *g_python_plugin_get_modname(const GPythonPlugin *plugin) +{ + char *result; /* Désignation brute à renvoyer*/ + char *filename; /* Chemin du script Python */ + char *name; /* Nom du fichier associé */ + size_t length; /* Taille du nom */ + int ret; /* Bilan d'une comparaison */ + + filename = g_python_plugin_get_filename(plugin); + + name = basename(filename); + + length = strlen(name); + +#define PYTHON_SUFFIX ".py" + + ret = strncmp(&name[length - STATIC_STR_SIZE(PYTHON_SUFFIX)], + PYTHON_SUFFIX, + STATIC_STR_SIZE(PYTHON_SUFFIX)); + + if (ret == 0) + name[length - STATIC_STR_SIZE(PYTHON_SUFFIX)] = '\0'; + + result = strdup(name); + + free(filename); + + return result; + +} + + + +/* ---------------------------------------------------------------------------------- */ +/* GLUE POUR CREATION DEPUIS PYTHON */ +/* ---------------------------------------------------------------------------------- */ + + +/****************************************************************************** +* * +* Paramètres : self = objet à initialiser (théoriquement). * +* args = arguments fournis à l'appel. * +* kwds = arguments de type key=val fournis. * +* * +* Description : Initialise une instance sur la base du dérivé de GObject. * +* * +* Retour : 0. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static int py_python_plugin_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + const char *version; /* Version du greffon */ + const char *url; /* Site Web associé */ + charp_array_t required; /* Liste des dépendances */ + const char *file; /* Emplacement du script */ + int ret; /* Bilan de lecture des args. */ + PyObject *value; /* Valeur d'un arg. implicite */ + const char *name; /* Désignation humaine courte */ + const char *desc; /* Description plus loquace */ + GPythonPlugin *plugin; /* Greffon à manipuler */ + + static char *kwlist[] = { "file", "version", "url", "required", NULL }; + +#define PYTHON_PLUGIN_DOC \ + "The PythonPlugin class helps to build custom Python plugins:\n" \ + "* some required information (*name* and *desc* for the parent" \ + " constructor) is automatically extracted from the final class name" \ + " or its documentation;\n" \ + "* implementations for pychrysalide.plugins.PluginModule._get_filename()" \ + " and pychrysalide.plugins.PluginModule._get_modname() are provided," \ + " relying on the *file* argument.\n" \ + "\n" \ + "Calls to the *__init__* constructor of this abstract object expect the" \ + " following arguments as keyword parameters:\n" \ + "* *file*: path to the Python script; the value should be equal to the" \ + " *__file__* keyword;\n" \ + "* *version* (optional): a string providing the version of the plugin;" \ + " Version format is free;\n" \ + "* *url* (optional): a string for the homepage describing the plugin;\n" \ + "* *required* (optional): dependencies of the plugin." + + /* Récupération des paramètres */ + + version = NULL; + url = NULL; + required.values = NULL; + required.length = 0; + + ret = PyArg_ParseTupleAndKeywords(args, kwds, "s|ssO&", kwlist, + &file, &version, &url, + convert_to_sequence_to_charp_array, &required); + if (!ret) return -1; + + name = self->ob_type->tp_name; + + value = PyObject_GetAttrString(self, "__doc__"); + + if (value != NULL && PyUnicode_Check(value)) + desc = PyUnicode_AsUTF8(value); + else + desc = NULL; + + Py_XDECREF(value); + + /* Initialisation d'un objet GLib */ + + ret = forward_pygobjet_init(self); + if (ret == -1) return -1; + + /* Eléments de base */ + + plugin = G_PYTHON_PLUGIN(pygobject_get(self)); + + STORE_PLUGIN_ABI(plugin); + + if (!g_python_plugin_create(plugin, name, desc, version, url, + &required.values, &required.length, + file)) + { + clean_charp_array(&required); + PyErr_SetString(PyExc_ValueError, _("Unable to create Python plugin.")); + return -1; + } + + clean_charp_array(&required); + + return 0; + +} + + + +/* ---------------------------------------------------------------------------------- */ +/* DEFINITION POUR SUPPORT PYTHON */ +/* ---------------------------------------------------------------------------------- */ + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Fournit un accès à une définition de type à diffuser. * +* * +* Retour : Définition d'objet pour Python. * +* * +* Remarques : - * +* * +******************************************************************************/ + +PyTypeObject *get_python_python_plugin_type(void) +{ + static PyMethodDef py_python_plugin_methods[] = { + { NULL } + }; + + static PyGetSetDef py_python_plugin_getseters[] = { + { NULL } + }; + + static PyTypeObject py_python_plugin_type = { + + PyVarObject_HEAD_INIT(NULL, 0) + + .tp_name = "pychrysalide.plugins.PythonPlugin", + .tp_basicsize = sizeof(PyGObject), + + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + + .tp_doc = PYTHON_PLUGIN_DOC, + + .tp_methods = py_python_plugin_methods, + .tp_getset = py_python_plugin_getseters, + + .tp_init = py_python_plugin_init, + .tp_new = py_python_plugin_new, + + }; + + return &py_python_plugin_type; + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Prend en charge l'objet 'pychrysalide.plugins.PythonPlugin'. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +bool ensure_python_python_plugin_is_registered(void) +{ + PyTypeObject *type; /* Type Python 'PythonPlugin' */ + PyObject *module; /* Module à recompléter */ + PyObject *dict; /* Dictionnaire du module */ + + type = get_python_python_plugin_type(); + + if (!PyType_HasFeature(type, Py_TPFLAGS_READY)) + { + module = get_access_to_python_module("pychrysalide.plugins"); + + dict = PyModule_GetDict(module); + + if (!ensure_python_plugin_module_is_registered()) + return false; + + if (!register_class_for_pygobject(dict, G_TYPE_PYTHON_PLUGIN, type)) + return false; + + } + + return true; + +} diff --git a/plugins/pychrysalide/plugins/python.h b/plugins/pychrysalide/plugins/python.h new file mode 100644 index 0000000..8613bd1 --- /dev/null +++ b/plugins/pychrysalide/plugins/python.h @@ -0,0 +1,57 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * python.h - prototypes pour la déclinaison Python de greffons + * + * Copyright (C) 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef _PLUGINS_PYCHRYSALIDE_PLUGINS_PYTHON_H +#define _PLUGINS_PYCHRYSALIDE_PLUGINS_PYTHON_H + + +#include <Python.h> +#include <stdbool.h> + + +#include <glibext/helpers.h> + + + +/* ------------------------- COMPOSITION DE NOUVEAU GREFFON ------------------------- */ + + +#define G_TYPE_PYTHON_PLUGIN (g_python_plugin_get_type()) + +DECLARE_GTYPE(GPythonPlugin, g_python_plugin, G, PYTHON_PLUGIN); + + + +/* ------------------------- DEFINITION POUR SUPPORT PYTHON ------------------------- */ + + +/* Fournit un accès à une définition de type à diffuser. */ +PyTypeObject *get_python_python_plugin_type(void); + +/* Prend en charge l'objet 'pychrysalide.plugins.PythonPlugin'. */ +bool ensure_python_python_plugin_is_registered(void); + + + +#endif /* _PLUGINS_PYCHRYSALIDE_PLUGINS_PYTHON_H */ |