summaryrefslogtreecommitdiff
path: root/plugins/pychrysalide/bindings.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/pychrysalide/bindings.c')
-rw-r--r--plugins/pychrysalide/bindings.c1466
1 files changed, 1466 insertions, 0 deletions
diff --git a/plugins/pychrysalide/bindings.c b/plugins/pychrysalide/bindings.c
new file mode 100644
index 0000000..7e87e27
--- /dev/null
+++ b/plugins/pychrysalide/bindings.c
@@ -0,0 +1,1466 @@
+
+/* 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"
+
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <pygobject.h>
+#include <stddef.h>
+#include <stdio.h>
+
+
+#include <common/cpp.h>
+#include <common/extstr.h>
+#include <core/core.h>
+#include <plugins/pglist.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"
+
+
+
+
+
+/* ------------------------ 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);
+
+/* Intègre les éventuelles fonctions natives des interfaces. */
+static void inherit_interface_slots(PyObject *);
+
+/**
+ * Conservation d'anciens pointeurs remplacés
+ */
+static initproc __old_gobject_meta_base_init = NULL;
+static initproc __old_gobject_meta_init = NULL;
+
+/**
+ * La fonction unhook_pygobject_behaviour(), inversant les opérations de la fonction
+ * unhook_pygobject_behaviour() manipulerait volontiers les fonctions PyImport_ImportModule()
+ * et PyObject_GetAttrString().
+ *
+ * Cependant, les appels à ces dernières depuis la clôture organisée par la fonction
+ * PyExit_pychrysalide() provoque l'erreur suivante :
+ *
+ * Fatal Python error: _PyInterpreterState_GET: the function must be called with the GIL held, but the GIL is released (the current Python thread state is NULL)
+ *
+ * Les accès nécessaires sont donc conservés ici.
+ */
+static PyTypeObject *__gobject_meta_base = NULL;
+static PyTypeObject *__gobject_meta = NULL;
+
+/* Interceptionne une initialisation pour types gi._gi.GObject. */
+static int hook_gobject_meta_base_init(PyObject *, PyObject *, PyObject *);
+
+/* Interceptionne une initialisation pour types GObject.Object. */
+static int hook_gobject_meta_init(PyObject *, PyObject *, PyObject *);
+
+/* Modifie légèrement le comportement des GObjects en Python. */
+static bool hook_pygobject_behaviour(void);
+
+/* Restaure le comportement d'origine des GObjects en Python. */
+static void unhook_pygobject_behaviour(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 ------------------------ */
+
+
+/* Assure le plein chargement dans un interpréteur Python. */
+static bool init_python_interpreter_for_standalone_mode(const pyinit_details_t *);
+
+/* 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 : cls = classe instanciée pour la construction d'un objet. *
+* *
+* Description : Intègre les éventuelles fonctions natives des interfaces. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void inherit_interface_slots(PyObject *cls)
+{
+ GType gtype; /* Type GObject lié au Python */
+ GType *ifaces; /* Interfaces implémentées */
+ guint ifaces_count; /* Nombre de ces interfaces */
+ guint i; /* Boucle de parcours */
+ PyTypeObject *iface_type; /* Type Python pour interface */
+ size_t k; /* Boucle de parcours */
+ size_t offset; /* Position dans une structure */
+ void *src_slot; /* Eventuelle fonction idéale */
+ void *dst_slot; /* Eventuelle fonction en place*/
+
+ static size_t slot_offsets[] = { /* Emplacements à actualiser */
+ //offsetof(PyTypeObject, tp_str),
+ offsetof(PyTypeObject, tp_hash),
+ offsetof(PyTypeObject, tp_richcompare),
+ };
+
+ /**
+ * Cette fonction reprend les principes de la fonction d'importation de
+ * PyGObject pygobject_inherit_slots().
+ *
+ * Cependant, cette dernière n'est appelée que depuis les fonctions :
+ * - pygobject_register_class() (send C -> Python), qui peut écraser des
+ * slots existants ;
+ * - pygobject_new_with_interfaces() / pygobject_lookup_class(), qui ne
+ * remplace pas les fonctions par défaut déjà en place.
+ *
+ * Pour mémoire, les types créés dynamiquement depuis des scripts (sens
+ * Python -> C) passent par la fonction _wrap_pyg_type_register().
+ */
+
+ gtype = pyg_type_from_object(cls);
+ assert(gtype != G_TYPE_INVALID);
+
+ ifaces = g_type_interfaces(gtype, &ifaces_count);
+
+ for (i = 0; i < ifaces_count; i++)
+ {
+ iface_type = pygobject_lookup_class(ifaces[i]);
+
+#define PYTYPE_SLOT(tp, off) \
+ *(void **)(void *)(((char *)tp) + off)
+
+ for (k = 0; k < ARRAY_SIZE(slot_offsets); k++)
+ {
+ offset = slot_offsets[k];
+
+ src_slot = PYTYPE_SLOT(iface_type, offset);
+
+ if (src_slot == NULL)
+ continue;
+
+ if (src_slot == PYTYPE_SLOT(&PyBaseObject_Type, offset)
+ || src_slot == PYTYPE_SLOT(&PyGObject_Type, offset))
+ continue;
+
+ dst_slot = PYTYPE_SLOT(cls, offset);
+
+ if (src_slot == dst_slot)
+ continue;
+
+ if (dst_slot != NULL)
+ {
+ if (dst_slot != PYTYPE_SLOT(&PyBaseObject_Type, offset)
+ && dst_slot != PYTYPE_SLOT(&PyGObject_Type, offset))
+ continue;
+ }
+
+ /**
+ * Usage du *(void **)(void *)
+ */
+ PYTYPE_SLOT(cls, offset) = src_slot;
+
+ }
+
+ }
+
+ g_free(ifaces);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : self = objet à initialiser (théoriquement). *
+* args = arguments fournis à l'appel. *
+* kwds = arguments de type key=val fournis. *
+* *
+* Description : Interceptionne une initialisation pour types gi._gi.GObject. *
+* *
+* Retour : Bilan de l'initialisation. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static int hook_gobject_meta_base_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ int result; /* Bilan à retourner */
+
+ /**
+ * Le type de self (self->ob_type->tp_name) est ici _GObjectMetaBase.
+ */
+
+ result = __old_gobject_meta_base_init(self, args, kwds);
+
+ if (result == 0)
+ inherit_interface_slots(self);
+
+ return result;
+
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : self = objet à initialiser (théoriquement). *
+* args = arguments fournis à l'appel. *
+* kwds = arguments de type key=val fournis. *
+* *
+* Description : Interceptionne une initialisation pour types GObject.Object. *
+* *
+* Retour : Bilan de l'initialisation. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static int hook_gobject_meta_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ int result; /* Bilan à retourner */
+
+ /**
+ * Le type de self (self->ob_type->tp_name) est ici GObjectMeta.
+ */
+
+ result = __old_gobject_meta_init(self, args, kwds);
+
+ if (result == 0)
+ inherit_interface_slots(self);
+
+ return result;
+
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : - *
+* *
+* Description : Modifie légèrement le comportement des GObjects en Python. *
+* *
+* Retour : Bilan de l'initialisation. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static bool hook_pygobject_behaviour(void)
+{
+ bool result; /* Bilan à retourner */
+ PyObject *gi_types_mod; /* Module Python-GObject */
+
+ result = false;
+
+ /**
+ * Validation des accès.
+ *
+ * Les références prises sur les attributs sont restituées dans
+ * unhook_pygobject_behaviour().
+ */
+
+ gi_types_mod = PyImport_ImportModule("gi.types");
+ if (gi_types_mod == NULL) goto exit;
+
+ __gobject_meta_base = (PyTypeObject *)PyObject_GetAttrString(gi_types_mod, "_GObjectMetaBase");
+ assert(__gobject_meta_base != NULL);
+ if (__gobject_meta_base == NULL) goto exit_with_mod;
+
+ __gobject_meta = (PyTypeObject *)PyObject_GetAttrString(gi_types_mod, "GObjectMeta");
+ assert(__gobject_meta != NULL);
+ if (__gobject_meta == NULL) goto exit_with_mod;
+
+ /**
+ * Modification des comportements.
+ */
+
+ __old_gobject_meta_base_init = __gobject_meta_base->tp_init;
+
+ __gobject_meta_base->tp_init = hook_gobject_meta_base_init;
+
+ __old_gobject_meta_init = __gobject_meta->tp_init;
+
+ __gobject_meta->tp_init = hook_gobject_meta_init;
+
+ result = true;
+
+ exit_with_mod:
+
+ Py_DECREF(gi_types_mod);
+
+ exit:
+
+ if (!result)
+ PyErr_SetString(PyExc_SystemError, "unable to hook the GObject behaviour in Python.");
+
+ return result;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : - *
+* *
+* Description : Restaure le comportement d'origine des GObjects en Python. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void unhook_pygobject_behaviour(void)
+{
+ /**
+ * Le déclenchement de la fonction PyExit_pychrysalide() appelante est
+ * programmé depuis init_python_pychrysalide_module(), appelée si
+ * hook_pygobject_behaviour() a opéré avec réussite.
+ */
+ assert(__gobject_meta_base != NULL);
+ assert(__gobject_meta != NULL);
+
+ __gobject_meta_base->tp_init = __old_gobject_meta_base_init;
+
+ Py_XDECREF(__gobject_meta_base);
+
+ __gobject_meta->tp_init = __old_gobject_meta_init;
+
+ Py_XDECREF(__gobject_meta);
+
+}
+
+
+/******************************************************************************
+* *
+* 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;
+
+ if (!hook_pygobject_behaviour())
+ 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 = init_python_interpreter_for_standalone_mode(details);
+
+ if (!status)
+ {
+ Py_DECREF(result);
+ result = NULL;
+ }
+
+ }
+
+ restore_original_pygobject_type(py_gobj_def);
+
+ exit:
+
+ if (result == NULL && !details->standalone)
+ log_pychrysalide_exception("Python bindings loading failed");
+
+ 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_EXT_ERROR, msg);
+
+ free(msg);
+
+ }
+
+}
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/* FONCTIONS GLOBALES DE CHRYSALIDE */
+/* ---------------------------------------------------------------------------------- */
+
+
+/******************************************************************************
+* *
+* Paramètres : details = précisions de chargement complémentaires. *
+* *
+* Description : Assure le plein chargement dans un interpréteur Python. *
+* *
+* Retour : Bilan de l'opération. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static bool init_python_interpreter_for_standalone_mode(const pyinit_details_t *details)
+{
+ bool result; /* Bilan à retourner */
+ int ret; /* Bilan de préparatifs */
+ Dl_info info; /* Informations dynamiques */
+ GModule *module; /* Structure de chargement GLib*/
+ GPluginModule *self; /* Représentation interne */
+
+ result = false;
+
+ ret = Py_AtExit(PyExit_pychrysalide);
+ if (ret == -1)
+ {
+ PyErr_SetString(PyExc_SystemError, "failed to register a cleanup function.");
+ goto exit;
+ }
+
+ if (!load_core_components(ACC_ALL_COMPONENTS))
+ {
+ PyErr_SetString(PyExc_SystemError, "unable to load core components.");
+ goto exit;
+ }
+
+ /**
+ * Le module chargé par Python n'apparaît pas dans la liste des greffons de
+ * Chrysalide et ne peut donc pas être référencé comme dépendance par d'autres
+ * extensions.
+ *
+ * Par ailleurs, lors de la recherche d'autres greffons via l'appel à la
+ * fonction init_all_plugins() ci-après, il faut que le nom du greffon soit
+ * déjà réservé pour faire échouer le second chargement du greffon courant
+ * lors du parcours des répertoires conservant les fichiers d'extensions.
+ */
+
+ ret = dladdr(__FUNCTION__, &info);
+ if (ret == 0)
+ {
+ LOG_ERROR_DL_N("dladdr");
+
+ PyErr_SetString(PyExc_SystemError, "failed to force bindings registration.");
+ goto exit;
+
+ }
+
+ module = g_module_open(info.dli_fname, G_MODULE_BIND_LAZY);
+ assert(module != NULL);
+
+ self = details->create_self(module);
+
+ /* A ce stade, le greffon a été chargé correctement */
+ g_plugin_module_override_flags(self, PSF_LOADED);
+
+ register_plugin(self);
+
+ unref_object(self);
+
+ /**
+ * Intégration des fonctionnalités portées par d'autres greffons.
+ */
+
+ result = true;
+
+ init_all_plugins(true);
+
+ exit:
+
+ return result;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : - *
+* *
+* Description : Point de sortie pour l'initialisation de Python. *
+* *
+* Retour : ? *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void PyExit_pychrysalide(void)
+{
+ unhook_pygobject_behaviour();
+
+ exit_all_plugins();
+
+ unload_core_components(ACC_ALL_COMPONENTS);
+
+}