/* 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 Chrysalide. If not, see . */ #include "pychrysa.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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" #include "mangling/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 : - * * * ******************************************************************************/ #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 (!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) status = add_mangling_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 '%s' 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); }