From 4ea4054f07ba5fa9be2b9a61ad0ca8f1c11afbd0 Mon Sep 17 00:00:00 2001 From: Cyrille Bagard Date: Thu, 20 Aug 2020 22:46:09 +0200 Subject: Renamed the main file of the Python plugin. --- plugins/pychrysalide/Makefile.am | 2 +- plugins/pychrysalide/core.c | 1030 ++++++++++++++++++++++++++++++++ plugins/pychrysalide/core.h | 63 ++ plugins/pychrysalide/core/logs.c | 2 +- plugins/pychrysalide/core/processors.c | 2 +- plugins/pychrysalide/gui/item.c | 2 +- plugins/pychrysalide/plugin.c | 2 +- plugins/pychrysalide/pychrysa.c | 1030 -------------------------------- plugins/pychrysalide/pychrysa.h | 63 -- 9 files changed, 1098 insertions(+), 1098 deletions(-) create mode 100644 plugins/pychrysalide/core.c create mode 100644 plugins/pychrysalide/core.h delete mode 100644 plugins/pychrysalide/pychrysa.c delete mode 100644 plugins/pychrysalide/pychrysa.h diff --git a/plugins/pychrysalide/Makefile.am b/plugins/pychrysalide/Makefile.am index 5d6c3e6..530a889 100644 --- a/plugins/pychrysalide/Makefile.am +++ b/plugins/pychrysalide/Makefile.am @@ -8,9 +8,9 @@ pychrysalide_la_SOURCES = \ access.h access.c \ constants.h constants.c \ constval.h constval.c \ + core.h core.c \ helpers.h helpers.c \ plugin.h plugin.c \ - pychrysa.h pychrysa.c \ star.h star.c \ strenum.h strenum.c \ struct.h struct.c \ diff --git a/plugins/pychrysalide/core.c b/plugins/pychrysalide/core.c new file mode 100644 index 0000000..fad21bf --- /dev/null +++ b/plugins/pychrysalide/core.c @@ -0,0 +1,1030 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * core.c - plugin permettant des extensions en Python + * + * Copyright (C) 2018-2019 Cyrille Bagard + * + * This file is part of Chrysalide. + * + * Chrysalide is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Chrysalide is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Chrysalide. If not, see . + */ + + +#include "core.h" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "access.h" +#include "constval.h" +#include "helpers.h" +#include "plugin.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 "debug/module.h" +#include "format/module.h" +#include "glibext/module.h" +#include "gtkext/module.h" +#include "gui/module.h" +#include "mangling/module.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_LOADED)); + + +/* Note la nature du chargement */ +static bool _standalone = true; + +/* Réceptacle pour le chargement forcé */ +static PyObject *_chrysalide_module = NULL; + +/* Conservation des informations du thread principal */ +static PyThreadState *_main_tstate = 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); + +/* Définit la version attendue de GTK à charger dans Python. */ +static bool set_version_for_gtk_namespace(const char *); + +/* Point de sortie pour l'initialisation de Python. */ +static void PyExit_pychrysalide(void); + +/* Ajoute le module 'plugins' au module Python. */ +static bool add_plugin_module_to_python_module(PyObject *); + +/* Complète les chemins de recherches de Python. */ +static void extend_python_path(const char *); + +/* Charge autant de greffons composés en Python que possible. */ +static void load_python_plugins(GPluginModule *); + + + +/****************************************************************************** +* * +* Paramètres : self = NULL car méthode statique. * +* args = non utilisé ici. * +* * +* Description : Fournit la révision du programme global. * +* * +* Retour : Numéro de révision. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_chrysalide_revision(PyObject *self, PyObject *args) +{ + PyObject *result; /* Valeur à retourner */ + +#define PY_CHRYSALIDE_REVISION_METHOD PYTHON_METHOD_DEF \ +( \ + revision, "/", \ + METH_NOARGS, py_chrysalide, \ + "Provide the revision number of Chrysalide.\n" \ + "\n" \ + "The returned value is provided as a string, for instance: 'r1665'." \ +) + + result = PyUnicode_FromString("r" XSTR(REVISION)); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : self = NULL car méthode statique. * +* args = non utilisé ici. * +* * +* Description : Fournit la version du programme global. * +* * +* Retour : Numéro de version. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_chrysalide_version(PyObject *self, PyObject *args) +{ + PyObject *result; /* Valeur à retourner */ + int major; /* Numéro de version majeur */ + int minor; /* Numéro de version mineur */ + int revision; /* Numéro de révision */ + char version[16]; /* Conservation temporaire */ + +#define PY_CHRYSALIDE_VERSION_METHOD PYTHON_METHOD_DEF \ +( \ + version, "/", \ + METH_NOARGS, py_chrysalide, \ + "Provide the version number of Chrysalide.\n" \ + "\n" \ + "The returned value is provided as a string, for instance: '1.6.65'." \ +) + + major = REVISION / 1000; + minor = (REVISION - (major * 1000)) / 100; + revision = REVISION % 100; + + snprintf(version, sizeof(version), "%d.%d.%d", major, minor, revision); + + result = PyUnicode_FromString(version); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : self = NULL car méthode statique. * +* args = non utilisé ici. * +* * +* Description : Fournit la version du greffon pour Python. * +* * +* Retour : Numéro de version. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static PyObject *py_chrysalide_mod_version(PyObject *self, PyObject *args) +{ + PyObject *result; /* Valeur à retourner */ + char version[16]; /* Conservation temporaire */ + +#define PY_CHRYSALIDE_MOD_VERSION_METHOD PYTHON_METHOD_DEF \ +( \ + mod_version, "/", \ + METH_NOARGS, py_chrysalide, \ + "Provide the version number of Chrysalide module for Python.\n" \ + "\n" \ + "The returned value is provided as a string, for instance: '0.1.0'." \ +) + + snprintf(version, sizeof(version), "%s", _chrysalide_plugin.version); + + result = PyUnicode_FromString(version); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Détermine si l'interpréteur lancé est celui pris en compte. * +* * +* Retour : true si l'exécution peut se poursuivre, false sinon. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool is_current_abi_suitable(void) +{ + bool result; + int fds[2]; + int ret; + char cmds[128]; + char content[64]; + ssize_t got; + +#define GRAB_ABI_FLAGS_IN_PYTHON \ + "import sys" "\n" \ + "import os" "\n" \ + "os.write(%d, bytes(sys.abiflags, 'UTF-8'))" "\n" + + result = false; + + ret = pipe(fds); + if (ret == -1) + { + perror("pipe()"); + return false; + } + + snprintf(cmds, sizeof(cmds), GRAB_ABI_FLAGS_IN_PYTHON, fds[1]); + + ret = PyRun_SimpleString(cmds); + if (ret != 0) goto icas_exit; + + got = read(fds[0], content, sizeof(content)); + if (got < 0) + { + perror("read()"); + goto icas_exit; + } + + content[got] = '\0'; + + result = (strcmp(content, LIBPYTHON_ABI_FLAGS) == 0); + + icas_exit: + + if (!result) + PyErr_SetString(PyExc_SystemError, "the ABI flags of the current interpreter do not match " \ + "the ones of the Python library used during the module compilation."); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : version = idenfiant de la version de GTK à stipuler. * +* * +* Description : Définit la version attendue de GTK à charger dans Python. * +* * +* Retour : Bilan de l'opération. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool set_version_for_gtk_namespace(const char *version) +{ + bool result; /* Bilan à retourner */ + PyObject *gi_mod; /* Module Python-GObject */ + PyObject *args; /* Arguments à fournir */ + + result = false; + + /** + * On cherche ici à éviter le message suivant si on charge 'gi.repository.Gtk' directement : + * + * + * PyGIWarning: Gtk was imported without specifying a version first. \ + * Use gi.require_version('Gtk', '3.0') before import to ensure that the right version gets loaded. + * + */ + + gi_mod = PyImport_ImportModule("gi"); + + if (gi_mod != NULL) + { + args = Py_BuildValue("ss", "Gtk", version); + + run_python_method(gi_mod, "require_version", args); + + result = (PyErr_Occurred() == NULL); + + Py_DECREF(args); + Py_DECREF(gi_mod); + + } + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Point de sortie pour l'initialisation de Python. * +* * +* Retour : ? * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void PyExit_pychrysalide(void) +{ + assert(_standalone); + + extern void set_current_project(void *project); + + set_current_project(NULL); + +#ifdef TRACK_GOBJECT_LEAKS + remember_gtypes_for_leaks(); +#endif + + exit_all_plugins(); + + unload_all_core_components(true); + +#ifdef TRACK_GOBJECT_LEAKS + dump_remaining_gtypes(); +#endif + +} + + +/****************************************************************************** +* * +* Paramètres : super = module dont la définition est à compléter. * +* * +* Description : Ajoute le module 'plugins' au module Python. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool add_plugin_module_to_python_module(PyObject *super) +{ + bool result; /* Bilan à retourner */ + PyObject *module; /* Sous-module mis en place */ + +#define PYCHRYSALIDE_PLUGINS_DOC \ + "Home for all plugins without another home." + + static PyModuleDef py_chrysalide_deguard_module = { + + .m_base = PyModuleDef_HEAD_INIT, + + .m_name = "pychrysalide.plugins", + .m_doc = PYCHRYSALIDE_PLUGINS_DOC, + + .m_size = -1, + + }; + + result = false; + + module = build_python_module(super, &py_chrysalide_deguard_module); + + result = (module != NULL); + + assert(result); + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Point d'entrée pour l'initialisation de Python. * +* * +* Retour : ? * +* * +* Remarques : - * +* * +******************************************************************************/ + +#define PYCHRYSALIDE_DOC \ + "PyChrysalide is a module containing Chrysalide's features and designed for Python users.\n" \ + "\n" \ + "The whole API is defined in a single library named 'pychrysalide.so' and can be used in two ways:\n" \ + "* either from the Chrysalide's GUI, by registering hooks or GLib signals;\n" \ + "* or from a shell command line, by setting PYTHONPATH to point to the directory containing the library.\n" \ + "\n" \ + "In both cases, this is a good start point to have a look at already existing plugins to quickly learn " \ + "how the API works.\n" \ + "\n" \ + "These plugins are located in the 'plugins/python' directory." + +PyMODINIT_FUNC PyInit_pychrysalide(void) +{ + PyObject *result; /* Module Python à retourner */ + bool status; /* Bilan des inclusions */ + int ret; /* Bilan de préparatifs */ + GPluginModule *self; /* Représentation interne */ + PluginStatusFlags self_flags; /* Fanions à mettre à jour */ + + static PyMethodDef py_chrysalide_methods[] = { + PY_CHRYSALIDE_REVISION_METHOD, + PY_CHRYSALIDE_VERSION_METHOD, + PY_CHRYSALIDE_MOD_VERSION_METHOD, + { NULL } + }; + + static PyModuleDef py_chrysalide_module = { + + .m_base = PyModuleDef_HEAD_INIT, + + .m_name = "pychrysalide", + .m_doc = PYCHRYSALIDE_DOC, + + .m_size = -1, + + .m_methods = py_chrysalide_methods + + }; + + /** + * Vérification préalable : dans le cas où on est embarqué directement dans + * un interpréteur Python, le module se charge et termine par charger à leur + * tour les différentes extensions trouvées, via load_remaning_plugins() puis + * chrysalide_plugin_on_native_loaded(). + * + * Lesquelles vont très probablement charger le module pychrysalide. + * + * Comme le chargement de ce dernier n'est alors pas encore terminé, + * Python va relancer cette procédure, et register_access_to_python_module() + * va détecter un doublon. + */ + + result = get_access_to_python_module(py_chrysalide_module.m_name); + + if (result != NULL) + { + Py_INCREF(result); + return result; + } + + if (!is_current_abi_suitable()) + goto exit; + + if (pygobject_init(-1, -1, -1) == NULL) + { + PyErr_SetString(PyExc_SystemError, "unable to init GObject in Python."); + goto exit; + } + + if (!set_version_for_gtk_namespace("3.0")) + goto exit; + + if (!load_all_core_components(true)) + { + PyErr_SetString(PyExc_SystemError, "unable to load all basic components."); + goto exit; + } + + /* Mise en place des fonctionnalités offertes */ + + result = PyModule_Create(&py_chrysalide_module); + + register_access_to_python_module(py_chrysalide_module.m_name, result); + + status = true; + + if (status) status = add_features_module(result); + + if (status) add_plugin_module_to_python_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_core_module(result); + if (status) status = add_debug_module(result); + if (status) status = add_format_module(result); + if (status) status = add_glibext_module(result); + if (status) status = add_gtkext_module(result); + if (status) status = add_gui_module(result); + if (status) status = add_mangling_module(result); + + if (status) status = ensure_python_plugin_module_is_registered(); + if (status) status = ensure_python_py_constval_is_registered(); + if (status) status = ensure_python_string_enum_is_registered(); + if (status) status = ensure_python_py_struct_is_registered(); + + if (status) status = populate_analysis_module(); + if (status) status = populate_arch_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(); + if (status) status = populate_glibext_module(); + if (status) status = populate_gtkext_module(); + if (status) status = populate_gui_module(); + if (status) status = populate_mangling_module(); + + if (!status) + { + PyErr_SetString(PyExc_SystemError, "failed to load all PyChrysalide components."); + Py_DECREF(result); + result = NULL; + goto exit; + } + + if (_standalone) + { + ret = Py_AtExit(PyExit_pychrysalide); + + if (ret == -1) + { + PyErr_SetString(PyExc_SystemError, "failed to register a cleanup function."); + Py_DECREF(result); + result = NULL; + goto exit; + } + + /** + * Comme les sources locales sont prioritaires, le fichier "core/global.h" + * du greffon masque la fonction suivante, issue du corps principal du + * programme. + * + * On la déclare donc à la main. + */ + extern void set_batch_mode(void); + + set_batch_mode(); + + init_all_plugins(false); + + lock_plugin_list_for_reading(); + + self = get_plugin_by_name("PyChrysalide", NULL); + assert(self != NULL); + + self_flags = g_plugin_module_get_flags(self); + self_flags &= ~(PSF_FAILURE | PSF_LOADED); + self_flags |= (status ? PSF_LOADED : PSF_FAILURE); + + g_plugin_module_override_flags(self, self_flags); + + unlock_plugin_list_for_reading(); + + load_remaning_plugins(); + + /** + * On laisse fuir ici la référence sur self afin d'avoir + * l'assurance que le greffon se déchargera toujours en dernier. + * + * La fuite mémoire est au final évitée dans PyExit_pychrysalide(). + */ + + } + + exit: + + if (result == NULL && !_standalone) + log_pychrysalide_exception("Loading failed"); + + return result; + +} + +/****************************************************************************** +* * +* Paramètres : path = chemin supplémentaire pour l'espace de recherche. * +* * +* Description : Complète les chemins de recherches de Python. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void extend_python_path(const char *path) +{ + PyObject *list; /* Liste de chemins à compléter*/ + PyObject *new; /* Nouveau chemin à intégrer */ + + list = PySys_GetObject("path"); + assert(list != NULL); + + new = PyUnicode_FromString(path); + assert(new != NULL); + + PyList_Append(list, new); + + Py_DECREF(new); + + add_to_env_var("PYTHONPATH", path, ":"); + +} + + +/****************************************************************************** +* * +* Paramètres : plugin = instance représentant le greffon Python d'origine. * +* * +* Description : Charge autant de greffons composés en Python que possible. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +static void load_python_plugins(GPluginModule *plugin) +{ +#ifdef DISCARD_LOCAL + char *edir; /* Répertoire de base effectif */ +#endif + DIR *dir; /* Répertoire à parcourir */ + char *paths; /* Emplacements de greffons */ + char *save; /* Sauvegarde pour ré-entrance */ + char *path; /* Chemin à fouiller */ + struct dirent *entry; /* Elément trouvé */ + char *modname; /* Nom du module pour Python */ + char *filename; /* Chemin d'accès reconstruit */ + GPluginModule *pyplugin; /* Lien vers un grffon Python */ + + /* Définition des zones d'influence */ + +#ifndef DISCARD_LOCAL + + extend_python_path(PACKAGE_SOURCE_DIR G_DIR_SEPARATOR_S "plugins" G_DIR_SEPARATOR_S "python"); + +#else + + edir = get_effective_directory(PLUGINS_DATA_DIR G_DIR_SEPARATOR_S "python"); + dir = opendir(edir); + free(edir); + + if (dir != NULL) + { + closedir(dir); + + edir = get_effective_directory(PLUGINS_DATA_DIR G_DIR_SEPARATOR_S "python"); + extend_python_path(edir); + free(edir); + + } + +#endif + + g_plugin_module_log_variadic_message(plugin, LMT_INFO, + _("PYTHONPATH environment variable set to '%s'"), + getenv("PYTHONPATH")); + + /* Chargements des extensions Python */ + + paths = get_env_var("PYTHONPATH"); + + save = NULL; /* gcc... */ + + for (path = strtok_r(paths, ":", &save); + path != NULL; + path = strtok_r(NULL, ":", &save)) + { + dir = opendir(path); + if (dir == NULL) + { + perror("opendir"); + continue; + } + + g_plugin_module_log_variadic_message(plugin, LMT_INFO, + _("Looking for Python plugins in '%s'..."), + path); + + while (1) + { + errno = 0; + + entry = readdir(dir); + + if (entry == NULL) + { + if (errno != 0) + perror("readdir"); + + break; + + } + + if (entry->d_type != DT_DIR) continue; + if (entry->d_name[0] == '.') continue; + + modname = strdup(entry->d_name); + modname = stradd(modname, "."); + modname = stradd(modname, "__init__"); + + filename = strdup(path); + filename = stradd(filename, G_DIR_SEPARATOR_S); + filename = stradd(filename, entry->d_name); + + pyplugin = g_python_plugin_new(modname, filename); + + if (pyplugin == NULL) + g_plugin_module_log_variadic_message(plugin, LMT_ERROR, + _("No suitable Python plugin found in '%s'"), + filename); + else + { + g_plugin_module_log_variadic_message(plugin, LMT_PROCESS, + _("Loaded the Python plugin found in the '%s' directory"), + filename); + + /** + * Comme le greffon n'est pas passé par la résolution des dépendances, + * on simule l'effet attendu. + */ + g_object_ref(G_OBJECT(plugin)); + + _register_plugin(pyplugin); + + } + + free(filename); + free(modname); + + } + + closedir(dir); + + } + + free(paths); + +} + + +/****************************************************************************** +* * +* Paramètres : plugin = greffon à manipuler. * +* * +* Description : Prend acte du chargement du greffon. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +G_MODULE_EXPORT bool chrysalide_plugin_init(GPluginModule *plugin) +{ + bool result; /* Bilan à retourner */ + 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(); + + PyEval_InitThreads(); + + PySys_SetArgv(0, (wchar_t *[]) { NULL }); + + _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); + + _main_tstate = PyThreadState_Get(); + + PyEval_ReleaseLock(); + + 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) +{ + clear_all_accesses_to_python_modules(); + + Py_XDECREF(_chrysalide_module); + +} + + +/****************************************************************************** +* * +* Paramètres : plugin = greffon à manipuler. * +* action = type d'action attendue. * +* * +* Description : Accompagne la fin du chargement des modules natifs. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +G_MODULE_EXPORT void chrysalide_plugin_on_native_loaded(GPluginModule *plugin, PluginAction action) +{ + PyThreadState *tstate; /* Contexte d'environnement */ + + if (!_standalone) + { + tstate = get_pychrysalide_main_tstate(); + PyEval_RestoreThread(tstate); + } + + load_python_plugins(plugin); + + if (!_standalone) + PyEval_SaveThread(); + +} + + +/****************************************************************************** +* * +* Paramètres : - * +* * +* Description : Fournit les informations du thread principal. * +* * +* Retour : Indications utiles à Python. * +* * +* Remarques : - * +* * +******************************************************************************/ + +PyThreadState *get_pychrysalide_main_tstate(void) +{ + PyThreadState *result; /* Indications à retourner */ + + result = _main_tstate; + + 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 */ + + 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 + * g_python_plugin_new() 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 new file mode 100644 index 0000000..368a7c3 --- /dev/null +++ b/plugins/pychrysalide/core.h @@ -0,0 +1,63 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * core.h - prototypes pour le plugin permettant des extensions en Python + * + * Copyright (C) 2018-2019 Cyrille Bagard + * + * This file is part of Chrysalide. + * + * Chrysalide is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Chrysalide is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Chrysalide. If not, see . + */ + + +#ifndef _PLUGINS_PYCHRYSALIDE_CORE_H +#define _PLUGINS_PYCHRYSALIDE_CORE_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 + + +#include +#include + + + +/* Point d'entrée pour l'initialisation de Python. */ +PyMODINIT_FUNC PyInit_pychrysalide(void); + +/* Prend acte du chargement du greffon. */ +G_MODULE_EXPORT bool chrysalide_plugin_init(GPluginModule *); + +/* 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_native_loaded(GPluginModule *, PluginAction); + +/* Fournit les informations du thread principal. */ +PyThreadState *get_pychrysalide_main_tstate(void); + +/* Présente dans le journal une exception survenue. */ +void log_pychrysalide_exception(const char *, ...); + + + +#endif /* _PLUGINS_PYCHRYSALIDE_CORE_H */ diff --git a/plugins/pychrysalide/core/logs.c b/plugins/pychrysalide/core/logs.c index 4e0bc7b..6d88693 100644 --- a/plugins/pychrysalide/core/logs.c +++ b/plugins/pychrysalide/core/logs.c @@ -34,8 +34,8 @@ #include "constants.h" #include "../access.h" +#include "../core.h" #include "../helpers.h" -#include "../pychrysa.h" diff --git a/plugins/pychrysalide/core/processors.c b/plugins/pychrysalide/core/processors.c index b1f85bc..fb867db 100644 --- a/plugins/pychrysalide/core/processors.c +++ b/plugins/pychrysalide/core/processors.c @@ -35,8 +35,8 @@ #include "../access.h" +#include "../core.h" #include "../helpers.h" -#include "../pychrysa.h" #include "../arch/processor.h" diff --git a/plugins/pychrysalide/gui/item.c b/plugins/pychrysalide/gui/item.c index 45190e4..2e32167 100644 --- a/plugins/pychrysalide/gui/item.c +++ b/plugins/pychrysalide/gui/item.c @@ -33,8 +33,8 @@ #include "../access.h" +#include "../core.h" #include "../helpers.h" -#include "../pychrysa.h" #include "../analysis/binary.h" #include "../gtkext/displaypanel.h" diff --git a/plugins/pychrysalide/plugin.c b/plugins/pychrysalide/plugin.c index 77647b5..463fece 100644 --- a/plugins/pychrysalide/plugin.c +++ b/plugins/pychrysalide/plugin.c @@ -38,8 +38,8 @@ #include "access.h" #include "constants.h" +#include "core.h" #include "helpers.h" -#include "pychrysa.h" #include "core/constants.h" diff --git a/plugins/pychrysalide/pychrysa.c b/plugins/pychrysalide/pychrysa.c deleted file mode 100644 index d1958ee..0000000 --- a/plugins/pychrysalide/pychrysa.c +++ /dev/null @@ -1,1030 +0,0 @@ - -/* Chrysalide - Outil d'analyse de fichiers binaires - * pychrysa.c - plugin permettant des extensions en Python - * - * Copyright (C) 2018-2019 Cyrille Bagard - * - * This file is part of Chrysalide. - * - * Chrysalide is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * Chrysalide is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Chrysalide. If not, see . - */ - - -#include "pychrysa.h" - - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#include "access.h" -#include "constval.h" -#include "helpers.h" -#include "plugin.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 "debug/module.h" -#include "format/module.h" -#include "glibext/module.h" -#include "gtkext/module.h" -#include "gui/module.h" -#include "mangling/module.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_LOADED)); - - -/* Note la nature du chargement */ -static bool _standalone = true; - -/* Réceptacle pour le chargement forcé */ -static PyObject *_chrysalide_module = NULL; - -/* Conservation des informations du thread principal */ -static PyThreadState *_main_tstate = 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); - -/* Définit la version attendue de GTK à charger dans Python. */ -static bool set_version_for_gtk_namespace(const char *); - -/* Point de sortie pour l'initialisation de Python. */ -static void PyExit_pychrysalide(void); - -/* Ajoute le module 'plugins' au module Python. */ -static bool add_plugin_module_to_python_module(PyObject *); - -/* Complète les chemins de recherches de Python. */ -static void extend_python_path(const char *); - -/* Charge autant de greffons composés en Python que possible. */ -static void load_python_plugins(GPluginModule *); - - - -/****************************************************************************** -* * -* Paramètres : self = NULL car méthode statique. * -* args = non utilisé ici. * -* * -* Description : Fournit la révision du programme global. * -* * -* Retour : Numéro de révision. * -* * -* Remarques : - * -* * -******************************************************************************/ - -static PyObject *py_chrysalide_revision(PyObject *self, PyObject *args) -{ - PyObject *result; /* Valeur à retourner */ - -#define PY_CHRYSALIDE_REVISION_METHOD PYTHON_METHOD_DEF \ -( \ - revision, "/", \ - METH_NOARGS, py_chrysalide, \ - "Provide the revision number of Chrysalide.\n" \ - "\n" \ - "The returned value is provided as a string, for instance: 'r1665'." \ -) - - result = PyUnicode_FromString("r" XSTR(REVISION)); - - return result; - -} - - -/****************************************************************************** -* * -* Paramètres : self = NULL car méthode statique. * -* args = non utilisé ici. * -* * -* Description : Fournit la version du programme global. * -* * -* Retour : Numéro de version. * -* * -* Remarques : - * -* * -******************************************************************************/ - -static PyObject *py_chrysalide_version(PyObject *self, PyObject *args) -{ - PyObject *result; /* Valeur à retourner */ - int major; /* Numéro de version majeur */ - int minor; /* Numéro de version mineur */ - int revision; /* Numéro de révision */ - char version[16]; /* Conservation temporaire */ - -#define PY_CHRYSALIDE_VERSION_METHOD PYTHON_METHOD_DEF \ -( \ - version, "/", \ - METH_NOARGS, py_chrysalide, \ - "Provide the version number of Chrysalide.\n" \ - "\n" \ - "The returned value is provided as a string, for instance: '1.6.65'." \ -) - - major = REVISION / 1000; - minor = (REVISION - (major * 1000)) / 100; - revision = REVISION % 100; - - snprintf(version, sizeof(version), "%d.%d.%d", major, minor, revision); - - result = PyUnicode_FromString(version); - - return result; - -} - - -/****************************************************************************** -* * -* Paramètres : self = NULL car méthode statique. * -* args = non utilisé ici. * -* * -* Description : Fournit la version du greffon pour Python. * -* * -* Retour : Numéro de version. * -* * -* Remarques : - * -* * -******************************************************************************/ - -static PyObject *py_chrysalide_mod_version(PyObject *self, PyObject *args) -{ - PyObject *result; /* Valeur à retourner */ - char version[16]; /* Conservation temporaire */ - -#define PY_CHRYSALIDE_MOD_VERSION_METHOD PYTHON_METHOD_DEF \ -( \ - mod_version, "/", \ - METH_NOARGS, py_chrysalide, \ - "Provide the version number of Chrysalide module for Python.\n" \ - "\n" \ - "The returned value is provided as a string, for instance: '0.1.0'." \ -) - - snprintf(version, sizeof(version), "%s", _chrysalide_plugin.version); - - result = PyUnicode_FromString(version); - - return result; - -} - - -/****************************************************************************** -* * -* Paramètres : - * -* * -* Description : Détermine si l'interpréteur lancé est celui pris en compte. * -* * -* Retour : true si l'exécution peut se poursuivre, false sinon. * -* * -* Remarques : - * -* * -******************************************************************************/ - -static bool is_current_abi_suitable(void) -{ - bool result; - int fds[2]; - int ret; - char cmds[128]; - char content[64]; - ssize_t got; - -#define GRAB_ABI_FLAGS_IN_PYTHON \ - "import sys" "\n" \ - "import os" "\n" \ - "os.write(%d, bytes(sys.abiflags, 'UTF-8'))" "\n" - - result = false; - - ret = pipe(fds); - if (ret == -1) - { - perror("pipe()"); - return false; - } - - snprintf(cmds, sizeof(cmds), GRAB_ABI_FLAGS_IN_PYTHON, fds[1]); - - ret = PyRun_SimpleString(cmds); - if (ret != 0) goto icas_exit; - - got = read(fds[0], content, sizeof(content)); - if (got < 0) - { - perror("read()"); - goto icas_exit; - } - - content[got] = '\0'; - - result = (strcmp(content, LIBPYTHON_ABI_FLAGS) == 0); - - icas_exit: - - if (!result) - PyErr_SetString(PyExc_SystemError, "the ABI flags of the current interpreter do not match " \ - "the ones of the Python library used during the module compilation."); - - return result; - -} - - -/****************************************************************************** -* * -* Paramètres : version = idenfiant de la version de GTK à stipuler. * -* * -* Description : Définit la version attendue de GTK à charger dans Python. * -* * -* Retour : Bilan de l'opération. * -* * -* Remarques : - * -* * -******************************************************************************/ - -static bool set_version_for_gtk_namespace(const char *version) -{ - bool result; /* Bilan à retourner */ - PyObject *gi_mod; /* Module Python-GObject */ - PyObject *args; /* Arguments à fournir */ - - result = false; - - /** - * On cherche ici à éviter le message suivant si on charge 'gi.repository.Gtk' directement : - * - * - * PyGIWarning: Gtk was imported without specifying a version first. \ - * Use gi.require_version('Gtk', '3.0') before import to ensure that the right version gets loaded. - * - */ - - gi_mod = PyImport_ImportModule("gi"); - - if (gi_mod != NULL) - { - args = Py_BuildValue("ss", "Gtk", version); - - run_python_method(gi_mod, "require_version", args); - - result = (PyErr_Occurred() == NULL); - - Py_DECREF(args); - Py_DECREF(gi_mod); - - } - - return result; - -} - - -/****************************************************************************** -* * -* Paramètres : - * -* * -* Description : Point de sortie pour l'initialisation de Python. * -* * -* Retour : ? * -* * -* Remarques : - * -* * -******************************************************************************/ - -static void PyExit_pychrysalide(void) -{ - assert(_standalone); - - extern void set_current_project(void *project); - - set_current_project(NULL); - -#ifdef TRACK_GOBJECT_LEAKS - remember_gtypes_for_leaks(); -#endif - - exit_all_plugins(); - - unload_all_core_components(true); - -#ifdef TRACK_GOBJECT_LEAKS - dump_remaining_gtypes(); -#endif - -} - - -/****************************************************************************** -* * -* Paramètres : super = module dont la définition est à compléter. * -* * -* Description : Ajoute le module 'plugins' au module Python. * -* * -* Retour : - * -* * -* Remarques : - * -* * -******************************************************************************/ - -static bool add_plugin_module_to_python_module(PyObject *super) -{ - bool result; /* Bilan à retourner */ - PyObject *module; /* Sous-module mis en place */ - -#define PYCHRYSALIDE_PLUGINS_DOC \ - "Home for all plugins without another home." - - static PyModuleDef py_chrysalide_deguard_module = { - - .m_base = PyModuleDef_HEAD_INIT, - - .m_name = "pychrysalide.plugins", - .m_doc = PYCHRYSALIDE_PLUGINS_DOC, - - .m_size = -1, - - }; - - result = false; - - module = build_python_module(super, &py_chrysalide_deguard_module); - - result = (module != NULL); - - assert(result); - - return result; - -} - - -/****************************************************************************** -* * -* Paramètres : - * -* * -* Description : Point d'entrée pour l'initialisation de Python. * -* * -* Retour : ? * -* * -* Remarques : - * -* * -******************************************************************************/ - -#define PYCHRYSALIDE_DOC \ - "PyChrysalide is a module containing Chrysalide's features and designed for Python users.\n" \ - "\n" \ - "The whole API is defined in a single library named 'pychrysalide.so' and can be used in two ways:\n" \ - "* either from the Chrysalide's GUI, by registering hooks or GLib signals;\n" \ - "* or from a shell command line, by setting PYTHONPATH to point to the directory containing the library.\n" \ - "\n" \ - "In both cases, this is a good start point to have a look at already existing plugins to quickly learn " \ - "how the API works.\n" \ - "\n" \ - "These plugins are located in the 'plugins/python' directory." - -PyMODINIT_FUNC PyInit_pychrysalide(void) -{ - PyObject *result; /* Module Python à retourner */ - bool status; /* Bilan des inclusions */ - int ret; /* Bilan de préparatifs */ - GPluginModule *self; /* Représentation interne */ - PluginStatusFlags self_flags; /* Fanions à mettre à jour */ - - static PyMethodDef py_chrysalide_methods[] = { - PY_CHRYSALIDE_REVISION_METHOD, - PY_CHRYSALIDE_VERSION_METHOD, - PY_CHRYSALIDE_MOD_VERSION_METHOD, - { NULL } - }; - - static PyModuleDef py_chrysalide_module = { - - .m_base = PyModuleDef_HEAD_INIT, - - .m_name = "pychrysalide", - .m_doc = PYCHRYSALIDE_DOC, - - .m_size = -1, - - .m_methods = py_chrysalide_methods - - }; - - /** - * Vérification préalable : dans le cas où on est embarqué directement dans - * un interpréteur Python, le module se charge et termine par charger à leur - * tour les différentes extensions trouvées, via load_remaning_plugins() puis - * chrysalide_plugin_on_native_loaded(). - * - * Lesquelles vont très probablement charger le module pychrysalide. - * - * Comme le chargement de ce dernier n'est alors pas encore terminé, - * Python va relancer cette procédure, et register_access_to_python_module() - * va détecter un doublon. - */ - - result = get_access_to_python_module(py_chrysalide_module.m_name); - - if (result != NULL) - { - Py_INCREF(result); - return result; - } - - if (!is_current_abi_suitable()) - goto exit; - - if (pygobject_init(-1, -1, -1) == NULL) - { - PyErr_SetString(PyExc_SystemError, "unable to init GObject in Python."); - goto exit; - } - - if (!set_version_for_gtk_namespace("3.0")) - goto exit; - - if (!load_all_core_components(true)) - { - PyErr_SetString(PyExc_SystemError, "unable to load all basic components."); - goto exit; - } - - /* Mise en place des fonctionnalités offertes */ - - result = PyModule_Create(&py_chrysalide_module); - - register_access_to_python_module(py_chrysalide_module.m_name, result); - - status = true; - - if (status) status = add_features_module(result); - - if (status) add_plugin_module_to_python_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_core_module(result); - if (status) status = add_debug_module(result); - if (status) status = add_format_module(result); - if (status) status = add_glibext_module(result); - if (status) status = add_gtkext_module(result); - if (status) status = add_gui_module(result); - if (status) status = add_mangling_module(result); - - if (status) status = ensure_python_plugin_module_is_registered(); - if (status) status = ensure_python_py_constval_is_registered(); - if (status) status = ensure_python_string_enum_is_registered(); - if (status) status = ensure_python_py_struct_is_registered(); - - if (status) status = populate_analysis_module(); - if (status) status = populate_arch_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(); - if (status) status = populate_glibext_module(); - if (status) status = populate_gtkext_module(); - if (status) status = populate_gui_module(); - if (status) status = populate_mangling_module(); - - if (!status) - { - PyErr_SetString(PyExc_SystemError, "failed to load all PyChrysalide components."); - Py_DECREF(result); - result = NULL; - goto exit; - } - - if (_standalone) - { - ret = Py_AtExit(PyExit_pychrysalide); - - if (ret == -1) - { - PyErr_SetString(PyExc_SystemError, "failed to register a cleanup function."); - Py_DECREF(result); - result = NULL; - goto exit; - } - - /** - * Comme les sources locales sont prioritaires, le fichier "core/global.h" - * du greffon masque la fonction suivante, issue du corps principal du - * programme. - * - * On la déclare donc à la main. - */ - extern void set_batch_mode(void); - - set_batch_mode(); - - init_all_plugins(false); - - lock_plugin_list_for_reading(); - - self = get_plugin_by_name("PyChrysalide", NULL); - assert(self != NULL); - - self_flags = g_plugin_module_get_flags(self); - self_flags &= ~(PSF_FAILURE | PSF_LOADED); - self_flags |= (status ? PSF_LOADED : PSF_FAILURE); - - g_plugin_module_override_flags(self, self_flags); - - unlock_plugin_list_for_reading(); - - load_remaning_plugins(); - - /** - * On laisse fuir ici la référence sur self afin d'avoir - * l'assurance que le greffon se déchargera toujours en dernier. - * - * La fuite mémoire est au final évitée dans PyExit_pychrysalide(). - */ - - } - - exit: - - if (result == NULL && !_standalone) - log_pychrysalide_exception("Loading failed"); - - return result; - -} - -/****************************************************************************** -* * -* Paramètres : path = chemin supplémentaire pour l'espace de recherche. * -* * -* Description : Complète les chemins de recherches de Python. * -* * -* Retour : - * -* * -* Remarques : - * -* * -******************************************************************************/ - -static void extend_python_path(const char *path) -{ - PyObject *list; /* Liste de chemins à compléter*/ - PyObject *new; /* Nouveau chemin à intégrer */ - - list = PySys_GetObject("path"); - assert(list != NULL); - - new = PyUnicode_FromString(path); - assert(new != NULL); - - PyList_Append(list, new); - - Py_DECREF(new); - - add_to_env_var("PYTHONPATH", path, ":"); - -} - - -/****************************************************************************** -* * -* Paramètres : plugin = instance représentant le greffon Python d'origine. * -* * -* Description : Charge autant de greffons composés en Python que possible. * -* * -* Retour : - * -* * -* Remarques : - * -* * -******************************************************************************/ - -static void load_python_plugins(GPluginModule *plugin) -{ -#ifdef DISCARD_LOCAL - char *edir; /* Répertoire de base effectif */ -#endif - DIR *dir; /* Répertoire à parcourir */ - char *paths; /* Emplacements de greffons */ - char *save; /* Sauvegarde pour ré-entrance */ - char *path; /* Chemin à fouiller */ - struct dirent *entry; /* Elément trouvé */ - char *modname; /* Nom du module pour Python */ - char *filename; /* Chemin d'accès reconstruit */ - GPluginModule *pyplugin; /* Lien vers un grffon Python */ - - /* Définition des zones d'influence */ - -#ifndef DISCARD_LOCAL - - extend_python_path(PACKAGE_SOURCE_DIR G_DIR_SEPARATOR_S "plugins" G_DIR_SEPARATOR_S "python"); - -#else - - edir = get_effective_directory(PLUGINS_DATA_DIR G_DIR_SEPARATOR_S "python"); - dir = opendir(edir); - free(edir); - - if (dir != NULL) - { - closedir(dir); - - edir = get_effective_directory(PLUGINS_DATA_DIR G_DIR_SEPARATOR_S "python"); - extend_python_path(edir); - free(edir); - - } - -#endif - - g_plugin_module_log_variadic_message(plugin, LMT_INFO, - _("PYTHONPATH environment variable set to '%s'"), - getenv("PYTHONPATH")); - - /* Chargements des extensions Python */ - - paths = get_env_var("PYTHONPATH"); - - save = NULL; /* gcc... */ - - for (path = strtok_r(paths, ":", &save); - path != NULL; - path = strtok_r(NULL, ":", &save)) - { - dir = opendir(path); - if (dir == NULL) - { - perror("opendir"); - continue; - } - - g_plugin_module_log_variadic_message(plugin, LMT_INFO, - _("Looking for Python plugins in '%s'..."), - path); - - while (1) - { - errno = 0; - - entry = readdir(dir); - - if (entry == NULL) - { - if (errno != 0) - perror("readdir"); - - break; - - } - - if (entry->d_type != DT_DIR) continue; - if (entry->d_name[0] == '.') continue; - - modname = strdup(entry->d_name); - modname = stradd(modname, "."); - modname = stradd(modname, "__init__"); - - filename = strdup(path); - filename = stradd(filename, G_DIR_SEPARATOR_S); - filename = stradd(filename, entry->d_name); - - pyplugin = g_python_plugin_new(modname, filename); - - if (pyplugin == NULL) - g_plugin_module_log_variadic_message(plugin, LMT_ERROR, - _("No suitable Python plugin found in '%s'"), - filename); - else - { - g_plugin_module_log_variadic_message(plugin, LMT_PROCESS, - _("Loaded the Python plugin found in the '%s' directory"), - filename); - - /** - * Comme le greffon n'est pas passé par la résolution des dépendances, - * on simule l'effet attendu. - */ - g_object_ref(G_OBJECT(plugin)); - - _register_plugin(pyplugin); - - } - - free(filename); - free(modname); - - } - - closedir(dir); - - } - - free(paths); - -} - - -/****************************************************************************** -* * -* Paramètres : plugin = greffon à manipuler. * -* * -* Description : Prend acte du chargement du greffon. * -* * -* Retour : - * -* * -* Remarques : - * -* * -******************************************************************************/ - -G_MODULE_EXPORT bool chrysalide_plugin_init(GPluginModule *plugin) -{ - bool result; /* Bilan à retourner */ - 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(); - - PyEval_InitThreads(); - - PySys_SetArgv(0, (wchar_t *[]) { NULL }); - - _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); - - _main_tstate = PyThreadState_Get(); - - PyEval_ReleaseLock(); - - 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) -{ - clear_all_accesses_to_python_modules(); - - Py_XDECREF(_chrysalide_module); - -} - - -/****************************************************************************** -* * -* Paramètres : plugin = greffon à manipuler. * -* action = type d'action attendue. * -* * -* Description : Accompagne la fin du chargement des modules natifs. * -* * -* Retour : - * -* * -* Remarques : - * -* * -******************************************************************************/ - -G_MODULE_EXPORT void chrysalide_plugin_on_native_loaded(GPluginModule *plugin, PluginAction action) -{ - PyThreadState *tstate; /* Contexte d'environnement */ - - if (!_standalone) - { - tstate = get_pychrysalide_main_tstate(); - PyEval_RestoreThread(tstate); - } - - load_python_plugins(plugin); - - if (!_standalone) - PyEval_SaveThread(); - -} - - -/****************************************************************************** -* * -* Paramètres : - * -* * -* Description : Fournit les informations du thread principal. * -* * -* Retour : Indications utiles à Python. * -* * -* Remarques : - * -* * -******************************************************************************/ - -PyThreadState *get_pychrysalide_main_tstate(void) -{ - PyThreadState *result; /* Indications à retourner */ - - result = _main_tstate; - - 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 */ - - 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 - * g_python_plugin_new() 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/pychrysa.h b/plugins/pychrysalide/pychrysa.h deleted file mode 100644 index a6d4bb1..0000000 --- a/plugins/pychrysalide/pychrysa.h +++ /dev/null @@ -1,63 +0,0 @@ - -/* Chrysalide - Outil d'analyse de fichiers binaires - * pychrysa.h - prototypes pour le plugin permettant des extensions en Python - * - * Copyright (C) 2018-2019 Cyrille Bagard - * - * This file is part of Chrysalide. - * - * Chrysalide is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * Chrysalide is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Chrysalide. If not, see . - */ - - -#ifndef _PLUGINS_PYCHRYSALIDE_PYCHRYSA_H -#define _PLUGINS_PYCHRYSALIDE_PYCHRYSA_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 - - -#include -#include - - - -/* Point d'entrée pour l'initialisation de Python. */ -PyMODINIT_FUNC PyInit_pychrysalide(void); - -/* Prend acte du chargement du greffon. */ -G_MODULE_EXPORT bool chrysalide_plugin_init(GPluginModule *); - -/* 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_native_loaded(GPluginModule *, PluginAction); - -/* Fournit les informations du thread principal. */ -PyThreadState *get_pychrysalide_main_tstate(void); - -/* Présente dans le journal une exception survenue. */ -void log_pychrysalide_exception(const char *, ...); - - - -#endif /* _PLUGINS_PYCHRYSALIDE_PYCHRYSA_H */ -- cgit v0.11.2-87-g4458