diff options
Diffstat (limited to 'plugins/pychrysalide/pychrysa.c')
-rw-r--r-- | plugins/pychrysalide/pychrysa.c | 616 |
1 files changed, 616 insertions, 0 deletions
diff --git a/plugins/pychrysalide/pychrysa.c b/plugins/pychrysalide/pychrysa.c new file mode 100644 index 0000000..35d14f4 --- /dev/null +++ b/plugins/pychrysalide/pychrysa.c @@ -0,0 +1,616 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * pychrysa.c - plugin permettant des extensions en Python + * + * Copyright (C) 2012-2017 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 Foobar. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "pychrysa.h" + + +#include <assert.h> +#include <errno.h> +#include <pygobject.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> + + +#include <config.h> +#include <common/cpp.h> +#include <common/environment.h> +#include <common/extstr.h> +#include <core/core.h> +#include <plugins/pglist.h> +#include <plugins/plugin-def.h> +#include <plugins/plugin-int.h> + + +#include "helpers.h" +#include "plugin.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" + + + +DEFINE_CHRYSALIDE_ACTIVE_PLUGIN("PyChrysalide", "Provides bindings to Python", "0.1.0", PGA_PLUGIN_INIT); + + +/* Note la nature du chargement */ +static bool _standalone = true; + +/* Réceptacle pour le chargement forcé */ +static PyObject *_chrysalide_module = NULL; + + +/* Fournit la révision du programme global. */ +static PyObject *py_chrysalide_revision(PyObject *, PyObject *); + +/* Fournit la version du programme global. */ +static PyObject *py_chrysalide_version(PyObject *, PyObject *); + +/* Fournit la version du greffon pour Python. */ +static PyObject *py_chrysalide_mod_version(PyObject *, PyObject *); + +/* Détermine si l'interpréteur lancé est celui pris en compte. */ +static bool is_current_abi_suitable(void); + +/* Définit la version attendue de GTK à charger dans Python. */ +static bool set_version_for_gtk_namespace(const char *); + +/* Charge autant de greffons composés en Python que possible. */ +static bool 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) +{ + return PyUnicode_FromString("r" XSTR(REVISION)); + +} + + +/****************************************************************************** +* * +* 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) +{ + char version[16]; + int major; + int minor; + int revision; + + major = REVISION / 1000; + minor = (REVISION - (major * 1000)) / 100; + revision = REVISION % 100; + + snprintf(version, sizeof(version), "%d.%d.%d", major, minor, revision); + + return PyUnicode_FromString(version); + +} + + +/****************************************************************************** +* * +* 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) +{ + char version[16]; + + snprintf(version, sizeof(version), "%s", _chrysalide_plugin.version); + + return PyUnicode_FromString(version); + +} + + +/****************************************************************************** +* * +* 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", "3.0"); + + 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 d'entrée pour l'initialisation de Python. * +* * +* Retour : ? * +* * +* Remarques : - * +* * +******************************************************************************/ +#include <gtkext/support.h> // REMME +#include <glibext/delayed.h> + +#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, it 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 */ + GPluginModule *self; /* Représentation interne */ + PluginStatusFlags self_flags; /* Fanions à mettre à jour */ + + static PyMethodDef py_chrysalide_methods[] = { + + { "revision", py_chrysalide_revision, + METH_NOARGS, + "revision(/)\n--\n\nProvide the revision number of Chrysalide." + }, + { "version", py_chrysalide_version, + METH_NOARGS, + "version(/)\n--\n\nProvide the version number of Chrysalide." + }, + { "mod_version", py_chrysalide_mod_version, + METH_NOARGS, + "mod_version(/)\n--\n\nProvide the version number of Chrysalide module for Python." + }, + { 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 + + }; + +#if 0 + do + { + int argc = 0; + //char *argv[] = { NULL }; + + /* Initialisation de GTK */ + g_set_prgname("Chrysalide"); + setlocale (LC_ALL, ""); + gtk_init(&argc, NULL/*&argv-*/); + + } while (0); + + add_pixmap_directory(PACKAGE_DATA_DIR); + add_pixmap_directory(PACKAGE_SOURCE_DIR G_DIR_SEPARATOR_S "pixmaps"); + + /** + * On évite d'initialiser deux fois... + */ + if (get_work_queue() != NULL) + init_work_queue(NULL/* !! */); +#endif + + + + + + if (!is_current_abi_suitable()) + return NULL; + + if (pygobject_init(-1, -1, -1) == NULL) + { + PyErr_SetString(PyExc_SystemError, "unable to init GObject in Python."); + return NULL; + } + + if (!set_version_for_gtk_namespace("3.0")) + return NULL; + + if (!load_all_basic_components()) + { + PyErr_SetString(PyExc_SystemError, "unable to load all basic components."); + return NULL; + } + + /* Mise en place des fonctionnalités offertes */ + + result = PyModule_Create(&py_chrysalide_module); + + status = register_python_py_struct(result); + + /* Interface 'LineGenerator' en premier... */ + if (status) status = add_glibext_module_to_python_module(result); + + /* BinRoutine hérite de BinSymbol... */ + if (status) status = add_format_module_to_python_module(result); + + if (status) status = register_python_plugin_module(result); + if (status) status = add_analysis_module_to_python_module(result); + if (status) status = add_arch_module_to_python_module(result); + if (status) status = add_common_module_to_python_module(result); + if (status) status = add_core_module_to_python_module(result); + if (status) status = add_debug_module_to_python_module(result); + if (status) status = add_gtkext_module_to_python_module(result); + if (status) status = add_gui_module_to_python_module(result); + + if (!status) + { + PyErr_SetString(PyExc_SystemError, "fail to load all PyChrysalide components."); + return NULL; + } + + if (_standalone) + { + 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(); + + } + + return result; + +} + + +/****************************************************************************** +* * +* Paramètres : plugin = instance représentant le greffon Python d'origine. * +* * +* Description : Charge autant de greffons composés en Python que possible. * +* * +* Retour : true. * +* * +* Remarques : - * +* * +******************************************************************************/ + +static bool load_python_plugins(GPluginModule *plugin) +{ + char *paths; /* Emplacements de greffons */ + char *save; /* Sauvegarde pour ré-entrance */ + char *path; /* Chemin à fouiller */ + DIR *dir; /* Répertoire à parcourir */ + 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 */ + + 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 '<b>%s</b>' directory"), + filename); + _register_plugin(pyplugin); + } + + free(filename); + free(modname); + + } + + closedir(dir); + + } + + return true; + +} + + +/****************************************************************************** +* * +* 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 */ + DIR *dir; /* Répertoire à parcourir */ + int ret; /* Bilan de préparatifs */ + + /* Définition des zones d'influence */ + + dir = opendir(PLUGINS_DIR G_DIR_SEPARATOR_S "python"); + + if (dir != NULL) + { + closedir(dir); + add_to_env_var("PYTHONPATH", PLUGINS_DIR G_DIR_SEPARATOR_S "python", ":"); + } + else + add_to_env_var("PYTHONPATH", PACKAGE_SOURCE_DIR G_DIR_SEPARATOR_S "plugins" \ + G_DIR_SEPARATOR_S "python", ":"); + + g_plugin_module_log_variadic_message(plugin, LMT_INFO, + _("PYTHONPATH environment variable set to '%s'"), + getenv("PYTHONPATH")); + + /* Chargement du module pour Python */ + + _standalone = false; + + ret = PyImport_AppendInittab("pychrysalide", &PyInit_pychrysalide); + + if (ret == -1) + { + g_plugin_module_log_variadic_message(plugin, LMT_ERROR, + _("Can not extend the existing table of Python built-in modules.")); + result = false; + goto cpi_done; + } + + Py_Initialize(); + + PySys_SetArgv(0, (wchar_t *[]) { NULL }); + + _chrysalide_module = PyImport_ImportModule("pychrysalide"); + + result = load_python_plugins(plugin); + + 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) +{ + Py_XDECREF(_chrysalide_module); + +} |