diff options
Diffstat (limited to 'plugins/pychrysalide/bindings.c')
-rw-r--r-- | plugins/pychrysalide/bindings.c | 1466 |
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); + +} |