From 262c95e0b088a56e9fd919edc57ad19f85e2e40e Mon Sep 17 00:00:00 2001 From: Cyrille Bagard Date: Mon, 26 Jan 2015 21:37:49 +0000 Subject: Begun to rewrite the whole plugins system. git-svn-id: svn://svn.gna.org/svn/chrysalide/trunk@461 abbe820e-26c8-41b2-8c08-b7b2b41f8b0a --- ChangeLog | 34 ++++++ configure.ac | 1 + plugins/Makefile.am | 2 +- plugins/devdbg/Makefile.am | 12 ++ plugins/devdbg/speed.c | 121 +++++++++++++++++++ plugins/devdbg/speed.h | 63 ++++++++++ plugins/pychrysa/debug/debugger.c | 2 + plugins/pychrysa/debug/debugger.h | 4 +- plugins/pychrysa/debug/module.c | 2 +- plugins/pychrysa/plugin.c | 17 +-- src/analysis/disass/disassembler.c | 49 +------- src/debug/debugger.c | 5 +- src/format/format.c | 5 +- src/plugins/pglist.c | 149 +++++++++++------------- src/plugins/pglist.h | 28 ++++- src/plugins/plugin-def.h | 194 ++++++++++++++++++++++++++++++- src/plugins/plugin-int.h | 29 +++-- src/plugins/plugin.c | 231 +++++++++++++++++-------------------- src/plugins/plugin.h | 21 ++-- 19 files changed, 670 insertions(+), 299 deletions(-) create mode 100644 plugins/devdbg/Makefile.am create mode 100644 plugins/devdbg/speed.c create mode 100644 plugins/devdbg/speed.h diff --git a/ChangeLog b/ChangeLog index e752ace..b358006 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,37 @@ +15-01-26 Cyrille Bagard + + * configure.ac: + Add the new Makefile from the 'plugins/devdbg' directory. + + * plugins/devdbg/Makefile.am: + * plugins/devdbg/speed.c: + * plugins/devdbg/speed.h: + New entries: introduce a new demo plugin, to measure disassembling time. + + * plugins/Makefile.am: + Add devdbg to SUBDIRS. + + * plugins/pychrysa/debug/debugger.c: + * plugins/pychrysa/debug/debugger.h: + * plugins/pychrysa/debug/module.c: + * plugins/pychrysa/plugin.c: + Update code. + + * src/analysis/disass/disassembler.c: + Remove some debug code and use the new plugins hooks. + + * src/debug/debugger.c: + * src/format/format.c: + Update code. + + * src/plugins/pglist.c: + * src/plugins/pglist.h: + * src/plugins/plugin.c: + * src/plugins/plugin-def.h: + * src/plugins/plugin.h: + * src/plugins/plugin-int.h: + Begin to rewrite the whole plugins system. + 15-01-25 Cyrille Bagard * src/analysis/disass/area.c: diff --git a/configure.ac b/configure.ac index 0461880..fa7246b 100644 --- a/configure.ac +++ b/configure.ac @@ -270,6 +270,7 @@ AC_CONFIG_FILES([Makefile pixmaps/Makefile plugins/Makefile plugins/androhelpers/Makefile + plugins/devdbg/Makefile plugins/govm/Makefile plugins/pychrysa/Makefile plugins/pychrysa/analysis/Makefile diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 4a6a852..84336ca 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -1,2 +1,2 @@ -SUBDIRS = androhelpers pychrysa python stackvars +SUBDIRS = androhelpers devdbg pychrysa python stackvars diff --git a/plugins/devdbg/Makefile.am b/plugins/devdbg/Makefile.am new file mode 100644 index 0000000..bd686d3 --- /dev/null +++ b/plugins/devdbg/Makefile.am @@ -0,0 +1,12 @@ + +lib_LTLIBRARIES = libspeed.la + +libspeed_la_SOURCES = \ + speed.h speed.c + +libspeed_la_CFLAGS = $(AM_CFLAGS) + + +AM_CPPFLAGS = $(LIBGTK_CFLAGS) $(LIBXML_CFLAGS) -I../../src + +AM_CFLAGS = $(DEBUG_CFLAGS) $(WARNING_FLAGS) $(COMPLIANCE_FLAGS) diff --git a/plugins/devdbg/speed.c b/plugins/devdbg/speed.c new file mode 100644 index 0000000..7f9705d --- /dev/null +++ b/plugins/devdbg/speed.c @@ -0,0 +1,121 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * speed.c - mesure de temps d'exécution internes + * + * Copyright (C) 2015 Cyrille Bagard + * + * This file is part of Chrysalide. + * + * OpenIDA 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. + * + * OpenIDA 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 . + */ + + +#include "speed.h" + + +#include +#include +#include +#include + + +#include +#include + + + +DEFINE_CHRYSALIDE_ACTIVE_PLUGIN("Speed Measure", "Tracks to time spent for disassembling code", "0.1.0", + PGA_DISASSEMBLY_STARTED, PGA_DISASSEMBLY_ENDED); + + +/* Mémorisation des résultats de chronométrages */ +typedef struct _speed_measure +{ + clock_t points[2]; /* Points de mesure successifs */ + unsigned long usages[2]; /* Taux d'utilisation du CPU */ + +} speed_measure; + + + +/****************************************************************************** +* * +* Paramètres : plugin = greffon à manipuler. * +* action = type d'action attendue. * +* binary = binaire dont le contenu est en cours de traitement. * +* * +* Description : Exécute une action pendant un désassemblage de binaire. * +* * +* Retour : - * +* * +* Remarques : - * +* * +******************************************************************************/ + +G_MODULE_EXPORT void process_binary_disassembly(const GPluginModule *plugin, PluginAction action, GLoadedBinary *binary) +{ + speed_measure *measure; /* Suivi des progressions */ + + void take_measure(clock_t *point, unsigned long *usage) + { + struct rusage rusage; /* Notification des usages */ + + *point = clock(); + + getrusage(RUSAGE_THREAD, &rusage); + + *usage = rusage.ru_utime.tv_sec * 1000000 + rusage.ru_utime.tv_usec; + *usage += rusage.ru_stime.tv_sec * 1000000 + rusage.ru_stime.tv_usec; + + } + + + switch (action) + { + case PGA_DISASSEMBLY_STARTED: + + measure = (speed_measure *)calloc(1, sizeof(speed_measure)); + g_object_set_data(G_OBJECT(binary), "speed_measure", measure); + + take_measure(&measure->points[0], &measure->usages[0]); + + break; + + + case PGA_DISASSEMBLY_ENDED: + + measure = (speed_measure *)g_object_get_data(G_OBJECT(binary), "speed_measure"); + + take_measure(&measure->points[1], &measure->usages[1]); + +#define SHOW_SPEED(pg, sm, title, p0, p1) \ + g_plugin_module_log_variadic_message(pg, LMT_INFO, title " : %.2g (%.2g)", \ + (double)(sm->points[p1] - sm->points[p0]) / CLOCKS_PER_SEC, \ + (sm->usages[p1] - sm->usages[p0]) / 1000000.0); + + SHOW_SPEED(plugin, measure, "Whole elapsed time for disassembly", 0, 1); + + g_object_set_data(G_OBJECT(binary), "speed_measure", NULL); + free(measure); + + break; + + default: + break; + + } + + printf("##########\n\nPassage 0x%08x !!!\n\n################\n", action); + +} diff --git a/plugins/devdbg/speed.h b/plugins/devdbg/speed.h new file mode 100644 index 0000000..bad7ba5 --- /dev/null +++ b/plugins/devdbg/speed.h @@ -0,0 +1,63 @@ + +/* Chrysalide - Outil d'analyse de fichiers binaires + * speed.h - prototypes pour la mesure de temps d'exécution internes + * + * Copyright (C) 2015 Cyrille Bagard + * + * This file is part of Chrysalide. + * + * OpenIDA 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. + * + * OpenIDA 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 . + */ + + +#ifndef _PLUGINS_DEVDBG_SPEED_H +#define _PLUGINS_DEVDBG_SPEED_H + + +#include + +#include +#include +#include + + + + +/* Exécute une action pendant un désassemblage de binaire. */ +G_MODULE_EXPORT void process_binary_disassembly(const GPluginModule *, PluginAction , GLoadedBinary *); + + + + + +#if 0 +#include +#include +#include + + + + +/* Initialise le greffon pour les bornes de routine. */ +G_MODULE_EXPORT bool init_plugin(GObject *); + +/* Fournit une indication sur le type d'opération(s) menée(s). */ +G_MODULE_EXPORT PluginAction get_plugin_action(void); + +/* Exécute une action définie sur un binaire chargé. */ +G_MODULE_EXPORT bool execute_action_on_binary(GLoadedBinary *, PluginAction); +#endif + + +#endif /* _PLUGINS_DEVDBG_SPEED_H */ diff --git a/plugins/pychrysa/debug/debugger.c b/plugins/pychrysa/debug/debugger.c index c829385..dde6639 100644 --- a/plugins/pychrysa/debug/debugger.c +++ b/plugins/pychrysa/debug/debugger.c @@ -25,6 +25,7 @@ #include "debugger.h" +#if 0 #include #include @@ -276,3 +277,4 @@ bool register_python_binary_debugger(PyObject *module) return (ret == 0); } +#endif diff --git a/plugins/pychrysa/debug/debugger.h b/plugins/pychrysa/debug/debugger.h index 378c675..1f7098a 100644 --- a/plugins/pychrysa/debug/debugger.h +++ b/plugins/pychrysa/debug/debugger.h @@ -29,7 +29,7 @@ #include #include - +#if 0 #include @@ -39,7 +39,7 @@ PyObject *py_binary_debugger_from_c(GBinaryDebugger *debugger); /* Ajoute l'objet 'pychrysalide.debug.BinaryDebugger' au module. */ bool register_python_binary_debugger(PyObject *); - +#endif #endif /* _PLUGINS_PYOIDA_DEBUG_DEBUGGER_H */ diff --git a/plugins/pychrysa/debug/module.c b/plugins/pychrysa/debug/module.c index 302ca38..9241ee0 100644 --- a/plugins/pychrysa/debug/module.c +++ b/plugins/pychrysa/debug/module.c @@ -59,7 +59,7 @@ bool add_debug_module_to_python_module(PyObject *super) result = (ret == 0); - result &= register_python_binary_debugger(module); + //result &= register_python_binary_debugger(module); return result; diff --git a/plugins/pychrysa/plugin.c b/plugins/pychrysa/plugin.c index 9a93642..60a9ad7 100644 --- a/plugins/pychrysa/plugin.c +++ b/plugins/pychrysa/plugin.c @@ -36,7 +36,7 @@ #include "helpers.h" #include "analysis/binary.h" -#include "debug/debugger.h" +//#include "debug/debugger.h" @@ -85,7 +85,7 @@ static bool g_python_plugin_execute_on_binary(GPythonPlugin *, GLoadedBinary *, /* Exécute une action relative à un débogueur. */ -static bool g_python_plugin_handle_debugger(const GPythonPlugin *, GBinaryDebugger *, PluginAction); +//static bool g_python_plugin_handle_debugger(const GPythonPlugin *, GBinaryDebugger *, PluginAction); @@ -126,7 +126,7 @@ static PyObject *pychrysa_plugin_is_matching(PyObject *, PyObject *); /* Exécute une action relative à un débogueur. */ -static PyObject *pychrysa_plugin_handle_debugger(PyObject *, PyObject *); +//static PyObject *pychrysa_plugin_handle_debugger(PyObject *, PyObject *); @@ -184,7 +184,7 @@ static void g_python_plugin_init(GPythonPlugin *plugin) plugin_parent = G_PLUGIN_MODULE(plugin); plugin_parent->exec_on_bin = (execute_action_on_binary_fc)g_python_plugin_execute_on_binary; - plugin_parent->handle_debugger = (execute_on_debugger_fc)g_python_plugin_handle_debugger; + //plugin_parent->handle_debugger = (execute_on_debugger_fc)g_python_plugin_handle_debugger; } @@ -241,9 +241,9 @@ GPluginModule *g_python_plugin_new(const char *modname, const char *filename) result = g_object_new(G_TYPE_PYTHON_PLUGIN, NULL); - G_PLUGIN_MODULE(result)->name = strdup(modname); - G_PLUGIN_MODULE(result)->name = stradd(G_PLUGIN_MODULE(result)->name, ".py"); - G_PLUGIN_MODULE(result)->filename = strdup(G_PLUGIN_MODULE(result)->name); + //G_PLUGIN_MODULE(result)->name = strdup(modname); + //G_PLUGIN_MODULE(result)->name = stradd(G_PLUGIN_MODULE(result)->name, ".py"); + //G_PLUGIN_MODULE(result)->filename = strdup(G_PLUGIN_MODULE(result)->name); G_PLUGIN_MODULE(result)->init = (init_plugin_fc)g_python_plugin_do_init; G_PLUGIN_MODULE(result)->get_action = (get_plugin_action_fc)g_python_plugin_get_action; @@ -484,7 +484,7 @@ static bool g_python_plugin_execute_on_binary(GPythonPlugin *plugin, GLoadedBina - +#if 0 /****************************************************************************** * * * Paramètres : plugin = greffon à consulter. * @@ -520,6 +520,7 @@ static bool g_python_plugin_handle_debugger(const GPythonPlugin *plugin, GBinary return result; } +#endif diff --git a/src/analysis/disass/disassembler.c b/src/analysis/disass/disassembler.c index 9907a52..126c116 100644 --- a/src/analysis/disass/disassembler.c +++ b/src/analysis/disass/disassembler.c @@ -192,11 +192,6 @@ static GDelayedDisassembly *g_delayed_disassembly_new(GLoadedBinary *binary, GBi * * ******************************************************************************/ -#include - -#include -#include - static void g_delayed_disassembly_process(GDelayedDisassembly *disass, GtkExtStatusBar *statusbar) { @@ -213,14 +208,9 @@ static void g_delayed_disassembly_process(GDelayedDisassembly *disass, GtkExtSta - clock_t begin, end; - double time_spent; - struct rusage usage; - unsigned long ustart; - unsigned long uend; - + routines = g_binary_format_get_routines(G_BIN_FORMAT(disass->format), &routines_count); /* Première étape */ @@ -229,12 +219,8 @@ static void g_delayed_disassembly_process(GDelayedDisassembly *disass, GtkExtSta + process_disassembly_event(PGA_DISASSEMBLY_STARTED, disass->binary); - begin = clock(); - - getrusage(RUSAGE_THREAD, &usage); - ustart = usage.ru_utime.tv_sec * 1000000 + usage.ru_utime.tv_usec; - ustart += usage.ru_stime.tv_sec * 1000000 + usage.ru_stime.tv_usec; *disass->instrs = disassemble_binary_content(disass->binary, statusbar); @@ -248,24 +234,13 @@ static void g_delayed_disassembly_process(GDelayedDisassembly *disass, GtkExtSta - getrusage(RUSAGE_THREAD, &usage); - uend = usage.ru_utime.tv_sec * 1000000 + usage.ru_utime.tv_usec; - uend += usage.ru_stime.tv_sec * 1000000 + usage.ru_stime.tv_usec; - - - end = clock(); - - time_spent = (double)(end - begin) / CLOCKS_PER_SEC; - - printf("[[ TIME ]] Disassembly :: %.2g (%.2g)\n", time_spent, (uend - ustart) / 1000000.0); - //gtk_extended_status_bar_remove(statusbar, id); - run_plugins_on_binary(disass->binary, PGA_BINARY_DISASSEMBLED, true); + //run_plugins_on_binary(disass->binary, PGA_BINARY_DISASSEMBLED, true); do @@ -301,13 +276,6 @@ static void g_delayed_disassembly_process(GDelayedDisassembly *disass, GtkExtSta qsort(routines, routines_count, sizeof(GBinRoutine *), (__compar_fn_t)g_binary_routine_compare); - begin = clock(); - - - getrusage(RUSAGE_THREAD, &usage); - ustart = usage.ru_utime.tv_sec * 1000000 + usage.ru_utime.tv_usec; - ustart += usage.ru_stime.tv_sec * 1000000 + usage.ru_stime.tv_usec; - print_disassembled_instructions(disass->buffer, disass->format, *disass->instrs, routines, routines_count, statusbar, id); @@ -316,21 +284,16 @@ static void g_delayed_disassembly_process(GDelayedDisassembly *disass, GtkExtSta - getrusage(RUSAGE_THREAD, &usage); - uend = usage.ru_utime.tv_sec * 1000000 + usage.ru_utime.tv_usec; - uend += usage.ru_stime.tv_sec * 1000000 + usage.ru_stime.tv_usec; - end = clock(); + process_disassembly_event(PGA_DISASSEMBLY_ENDED, disass->binary); - time_spent = (double)(end - begin) / CLOCKS_PER_SEC; - - printf("[[ TIME ]] Printing :: %.2g (%.2g)\n", time_spent, (uend - ustart) / 1000000.0); + printf("---fin\n"); //gtk_extended_status_bar_remove(statusbar, id); - run_plugins_on_binary(disass->binary, PGA_BINARY_PRINTED, true); + //run_plugins_on_binary(disass->binary, PGA_BINARY_PRINTED, true); diff --git a/src/debug/debugger.c b/src/debug/debugger.c index a423f76..c268bcf 100644 --- a/src/debug/debugger.c +++ b/src/debug/debugger.c @@ -158,12 +158,15 @@ bool g_binary_debugger_attach(GBinaryDebugger *debugger) if (debugger->attach == NULL) result = true; else result = debugger->attach(debugger); - pglist = get_all_plugins_for_action(PGA_DEBUGGER_ATTACH, &pgcount); + pgcount = 0; + pglist = NULL;//get_all_plugins_for_action(PGA_DEBUGGER_ATTACH, &pgcount); if (pgcount > 0) { + /* for (i = 0; i < pgcount; i++) g_plugin_module_handle_debugger(pglist[i], debugger, PGA_DEBUGGER_ATTACH); + */ free(pglist); diff --git a/src/format/format.c b/src/format/format.c index 8e1b9f4..e710668 100644 --- a/src/format/format.c +++ b/src/format/format.c @@ -519,14 +519,15 @@ GBinFormat *load_new_format(FormatType type, const char *filename, bin_t **conte tmp = strdup(filename); - pglist = get_all_plugins_for_action(PGA_FORMAT_MATCHER, &pgcount); + pgcount = 0; + pglist = NULL;//get_all_plugins_for_action(PGA_FORMAT_MATCHER, &pgcount); if (pgcount > 0) { lnf_rescan: for (i = 0; i < pgcount; i++) - switch (g_plugin_module_is_matching(pglist[i], &tmp, content, length)) + switch (0/*g_plugin_module_is_matching(pglist[i], &tmp, content, length)*/) { case MFA_MATCHED: /* FIXME */ diff --git a/src/plugins/pglist.c b/src/plugins/pglist.c index 9286085..70fc66a 100644 --- a/src/plugins/pglist.c +++ b/src/plugins/pglist.c @@ -25,6 +25,7 @@ #include "pglist.h" +#include #include #include #include @@ -37,14 +38,23 @@ #include "../common/extstr.h" +/* Représentation dédiée aux listes de greffons */ +typedef struct _pg_array +{ + PluginAction action; /* Action commune ou PGA_ALL */ + + GPluginModule **plugins; /* Liste de greffons */ + size_t plugins_count; /* Taille de cette liste */ + +} pg_array; /* Propriétés de l'ensemble des greffons */ typedef struct _plugins_list { GObject *ref; /* Référencement global */ - GPluginModule **plugins; /* Liste de greffons */ - size_t plugins_count; /* Taille de cette liste */ + pg_array *all; /* Liste de tous les greffons */ + pg_array sorted[PGA_COUNT]; /* Tri par catégories */ } plugins_list; @@ -75,9 +85,17 @@ void browse_directory_for_plugins(plugins_list *, const char *); bool init_all_plugins(GObject *ref) { + size_t i; /* Boucle de parcours */ + _list.ref = ref; g_object_ref(ref); + for (i = 0; i < PGA_COUNT; i++) + _list.sorted[i].action = PGA_EMPTY; + + _list.sorted[0].action = PGA_ALL; + _list.all = &_list.sorted[0]; + browse_directory_for_plugins(&_list, PACKAGE_SOURCE_DIR "/plugins"); return true; @@ -101,11 +119,11 @@ void exit_all_plugins(void) { size_t i; /* Boucle de parcours */ - for (i = 0; i < _list.plugins_count; i++) - g_object_unref(_list.plugins[i]); + for (i = 0; i < _list.all->plugins_count; i++) + g_object_unref(_list.all->plugins[i]); - if (_list.plugins != NULL) - free(_list.plugins); + for (i = 0; i < PGA_COUNT; i++) + free(_list.sorted[i].plugins); g_object_unref(_list.ref); @@ -199,60 +217,30 @@ void browse_directory_for_plugins(plugins_list *list, const char *dir) /****************************************************************************** * * * Paramètres : action = fonctionnalité recherchée. * -* * -* Description : Founit un greffon offrant le service demandé. * -* * -* Retour : Greffon satisfaisant ou NULL si aucun. * -* * -* Remarques : - * -* * -******************************************************************************/ - -GPluginModule *get_one_plugin_for_action(PluginAction action) -{ - GPluginModule *result; /* Greffon à retourner */ - size_t i; /* Boucle de parcours */ - - result = NULL; - - for (i = 0; i < _list.plugins_count; i++) - if (g_plugin_module_get_action(_list.plugins[i]) & action) - { - result = _list.plugins[i]; - break; - } - - return result; - -} - - -/****************************************************************************** -* * -* Paramètres : action = fonctionnalité recherchée. * * count = nombre de greffons trouvés. [OUT] * * * * Description : Founit les greffons offrant le service demandé. * * * -* Retour : Liste de greffons correspondants à libérer de la mémoire. * +* Retour : Liste de greffons correspondants issue d'un tri interne. * * * * Remarques : - * * * ******************************************************************************/ -GPluginModule **get_all_plugins_for_action(PluginAction action, size_t *count) +const GPluginModule **get_all_plugins_for_action(PluginAction action, size_t *count) { - GPluginModule **result; /* Greffon à retourner */ + const GPluginModule **result; /* Greffon à retourner */ size_t i; /* Boucle de parcours */ result = NULL; *count = 0; - for (i = 0; i < _list.plugins_count; i++) - if (g_plugin_module_get_action(_list.plugins[i]) & action) + for (i = 0; i < PGA_COUNT; i++) + if (_list.sorted[i].action == action) { - result = (GPluginModule **)realloc(result, ++(*count) * sizeof(GPluginModule *)); - result[*count - 1] = _list.plugins[i]; + result = (const GPluginModule **)_list.sorted[i].plugins; + *count = _list.sorted[i].plugins_count; + break; } return result; @@ -274,57 +262,50 @@ GPluginModule **get_all_plugins_for_action(PluginAction action, size_t *count) void add_plugin_to_main_list(GPluginModule *plugin) { - plugins_list *list; /* Liste à modifier */ + const plugin_interface *interface; /* Informations à consulter */ + size_t i; /* Boucle de parcours #1 */ + size_t j; /* Boucle de parcours #2 */ - list = &_list; + interface = g_plugin_module_get_interface(plugin); - if (plugin->init != NULL && !plugin->init(plugin, list->ref)) + void add_plugin_into_array(pg_array *array, GPluginModule *pg) { - log_variadic_message(LMT_ERROR, _("Initialization of plugin '%s' failed !"), - plugin->filename); - g_object_unref(G_OBJECT(plugin)); - return; - } + array->plugins = (GPluginModule **)realloc(array->plugins, + ++array->plugins_count * sizeof(GPluginModule)); - list->plugins = (GPluginModule **)realloc(list->plugins, ++list->plugins_count * sizeof(GPluginModule *)); - list->plugins[list->plugins_count - 1] = plugin; + array->plugins[array->plugins_count - 1] = pg; -} + } + /* FIXME : lock */ -/****************************************************************************** -* * -* Paramètres : binary = binaire chargé en mémoire à traiter. * -* action = fonctionnalité recherchée. * -* lock = indique si l'exécution n'est pas celle principale. * -* * -* Description : Opère une action donnée sur un binaire défini. * -* * -* Retour : - * -* * -* Remarques : - * -* * -******************************************************************************/ + add_plugin_into_array(_list.all, plugin); -void run_plugins_on_binary(GLoadedBinary *binary, PluginAction action, bool lock) -{ - size_t i; /* Boucle de parcours */ + for (i = 0; i < interface->actions_count; i++) + { + if (interface->actions[i] == PGA_ALL) continue; - /* - if (lock) - gdk_threads_enter(); - */ + for (j = 1; j < PGA_COUNT; j++) + { + if (_list.sorted[j].action == interface->actions[i]) + { + add_plugin_into_array(&_list.sorted[j], plugin); + break; + } + + else if (_list.sorted[j].action == PGA_EMPTY) + { + add_plugin_into_array(&_list.sorted[j], plugin); + _list.sorted[j].action = interface->actions[i]; + break; + } - for (i = 0; i < _list.plugins_count; i++) - if (g_plugin_module_get_action(_list.plugins[i]) & action) - g_plugin_module_execute_action_on_binary(_list.plugins[i], binary, action); + } + + assert(j < PGA_COUNT); - /* - if (lock) - { - gdk_flush(); - gdk_threads_leave(); } - */ + + /* FIXME : lock */ } diff --git a/src/plugins/pglist.h b/src/plugins/pglist.h index 4964d1d..fec31c2 100644 --- a/src/plugins/pglist.h +++ b/src/plugins/pglist.h @@ -41,14 +41,30 @@ bool init_all_plugins(GObject *); /* Procède au déchargement des différents greffons présents. */ void exit_all_plugins(void); -/* Founit un greffon offrant le service demandé. */ -GPluginModule *get_one_plugin_for_action(PluginAction); - /* Founit less greffons offrant le service demandé. */ -GPluginModule **get_all_plugins_for_action(PluginAction, size_t *) __attribute__ ((deprecated)); +const GPluginModule **get_all_plugins_for_action(PluginAction, size_t *); + + +/** + * Définitions des opérations appliquables à une catégories de greffons. + */ -/*Opère une action donnée sur un binaire défini. */ -void run_plugins_on_binary(GLoadedBinary *, PluginAction, bool); +#define process_all__plugins_for(a, f, ...) \ + do \ + { \ + size_t __count; \ + const GPluginModule **__list; \ + size_t __i; \ + __list = get_all_plugins_for_action(a, &__count); \ + for (__i = 0; __i < __count; __i++) \ + f(__list[__i], a, __VA_ARGS__); \ + } \ + while (0) + +/* DPS_DISASSEMBLY */ + +#define process_disassembly_event(a, b) \ + process_all__plugins_for(a, g_plugin_module_process_disassembly_event, b) diff --git a/src/plugins/plugin-def.h b/src/plugins/plugin-def.h index 176c55d..d1a13b7 100644 --- a/src/plugins/plugin-def.h +++ b/src/plugins/plugin-def.h @@ -26,8 +26,127 @@ #define _PLUGINS_PLUGIN_DEF_H -#include "../analysis/binary.h" -#include "../debug/debugger.h" +#include +#include + + + + + +/* ------------------------ IDENTIFICATION DE COMPATIBILITES ------------------------ */ + + +/* Version identifiant les définitions courantes */ +typedef uint32_t plugin_abi_version_t; + +#define DEFINE_PLUGIN_ABI_VERSION(maj, min, rev) \ + (((maj & 0xff) << 24) | ((min & 0xff) << 16) | (rev & 0xffff)) + +#define GET_ABI_MAJ_VERSION(vs) ((vs >> 24) & 0xff) +#define GET_ABI_MIN_VERSION(vs) ((vs >> 16) & 0xff) +#define GET_ABI_REV_VERSION(vs) (vs & 0xffff) + +#define CURRENT_ABI_VERSION DEFINE_PLUGIN_ABI_VERSION(0, 1, 0) + +//#define HARD_CODE_CURRENT_ABI_VERSION const plugin_abi_version_t abi_version = CURRENT_ABI_VERSION + + + +/* ------------------------- DEFINITION D'UN PROJET INTERNE ------------------------- */ + + +/* Idenifiant d'une action menée */ +typedef uint32_t plugin_action_t; + +#define DEFINE_PLUGIN_CATEGORY(cat) ((cat & 0xff) << 24) +#define DEFINE_PLUGIN_SUB_CATEGORY(sub) ((sub & 0xff) << 16) +#define DEFINE_PLUGIN_ACTION(a) (a & 0xffff) + +#define GET_PLUGIN_CATEGORY(val) ((val >> 24) & 0xff) +#define GET_PLUGIN_SUB_CATEGORY(val) ((val >> 16) & 0xff) + +#define MASK_PLUGIN_CATEGORY(val) (val & (0xff << 24)) +#define MASK_PLUGIN_SUB_CATEGORY(val) (val & (0xff << 16)) + + +#define DPC_NONE DEFINE_PLUGIN_CATEGORY(0) +#define DPC_BINARY_PROCESSING DEFINE_PLUGIN_CATEGORY(1) + +// GUI + +/* DPC_NONE */ + +#define DPS_NONE DEFINE_PLUGIN_SUB_CATEGORY(0) + +/* DPC_BINARY_PROCESSING */ + +#define DPS_FORMAT DEFINE_PLUGIN_SUB_CATEGORY(0) +#define DPS_DISASSEMBLY DEFINE_PLUGIN_SUB_CATEGORY(1) + +// GUI -> project +// binary loaded +// binary unload + +// GUI -> dialog box + + + +/* Action(s) menée(s) par un greffon */ +typedef enum _PluginAction +{ + /* Aucun intérêt */ + PGA_NONE = DPC_NONE | DPS_NONE | DEFINE_PLUGIN_ACTION(0), + + /** + * DPC_BINARY_PROCESSING | DPS_FORMAT + */ + + /* Détection et chargement */ + PGA_FORMAT_MATCHER = DPC_BINARY_PROCESSING | DPS_FORMAT | DEFINE_PLUGIN_ACTION(0), + + /** + * DPC_BINARY_PROCESSING | DPS_DISASSEMBLY + */ + + /* Désassemblage démarré */ + PGA_DISASSEMBLY_STARTED = DPC_BINARY_PROCESSING | DPS_DISASSEMBLY | DEFINE_PLUGIN_ACTION(0), + + /* Désassemblage fini */ + PGA_DISASSEMBLY_ENDED = DPC_BINARY_PROCESSING | DPS_DISASSEMBLY | DEFINE_PLUGIN_ACTION(1), + + + + PGA_DISASSEMBLE = (1 << 1), /* Désassemblage (non trivial) */ + + PGA_BINARY_DISASSEMBLED = (1 << 2), /* Désassemblage fini */ + PGA_BINARY_LINKED = (1 << 3), /* Liaison en place */ + PGA_BINARY_BOUNDED = (1 << 4), /* Limites de routines définies*/ + PGA_BINARY_GROUPED = (1 << 5), /* Instructions regroupées */ + PGA_BINARY_PRINTED = (1 << 6), /* Instructions imprimées */ + + PGA_DISASS_PROCESS = (1 << 6), /* Traitement niveau assembleur*/ + PGA_CODE_PROCESS = (1 << 7), /* Traitement du code existant */ + + PGA_DEBUGGER_ATTACH = (1 << 8), /* Activation d'un débogueur */ + PGA_DEBUGGER_DETACH = (1 << 9), /* Désactivation d'un débogueur*/ + + /** + * Organisation interne : + * - rassemblement massif de tous les greffons. + * - marquage des cellules vides. + */ + PGA_ALL = 0xfffffffe, + PGA_EMPTY = 0xffffffff + +} PluginAction; + + + +/* MAJ !! */ +#define PGA_COUNT 6 + + + @@ -41,6 +160,7 @@ typedef enum _PluginType /* Action(s) menée(s) par le greffon */ +#if 0 typedef enum _PluginAction { PGA_NONE = (0 << 0), /* Aucun intérêt */ @@ -61,8 +181,9 @@ typedef enum _PluginAction PGA_DEBUGGER_ATTACH = (1 << 8), /* Activation d'un débogueur */ PGA_DEBUGGER_DETACH = (1 << 9) /* Désactivation d'un débogueur*/ - } PluginAction; +#endif + /* Actions éligibles pour run_plugins_on_binary() */ @@ -74,7 +195,7 @@ typedef enum _PluginAction /* Fournit une indication sur le(s) type(s) du greffon présent. */ -typedef PluginType (* get_plugin_type_fc) (void); +//typedef PluginType (* get_plugin_type_fc) (void); /* Fournit une indication sur le type d'opération(s) menée(s). */ //typedef PluginAction (* get_plugin_action_fc) (void); @@ -98,4 +219,69 @@ typedef enum _MatchingFormatAction + +/* ------------------------ PREMIER INTERFACAGE PROTOCOLAIRE ------------------------ */ + + +#define CHRYSALIDE_PLUGIN_MAGIC 0xdeadc0de1234abcdull + + +/* Définition d'un greffon */ +typedef struct _plugin_interface +{ + uint64_t magic; /* Vérification a minima */ + plugin_abi_version_t abi_version; /* Version du protocole utilisé*/ + + const char *name; /* Désignation humaine courte */ + const char *desc; /* Description plus loquace */ + const char *version; /* Version du greffon */ + + const char **required; /* Pré-chargements requis */ + size_t required_count; /* Quantité de ces dépendances */ + /* status */ + + plugin_action_t *actions; /* Liste des actions gérées */ + size_t actions_count; /* Quantité de ces actions */ + +} plugin_interface; + + +/* Facilitations de déclarations */ + +#define EMPTY_PG_LIST(name) \ + name = NULL, \ + name ## _count = 0 \ + +#define BUILD_PG_LIST(name, lst) \ + name = lst, \ + name ## _count = sizeof(lst) / sizeof(lst[0]) \ + +#define AL(...) BUILD_PG_LIST(.actions, ((plugin_action_t []){ __VA_ARGS__ })) + +#define RL(...) BUILD_PG_LIST(.required, ((char *[]){ __VA_ARGS__ })) + + +#define DEFINE_CHRYSALIDE_PLUGIN(n, d, v, r, a) \ +G_MODULE_EXPORT const plugin_interface _chrysalide_plugin = { \ + \ + .magic = CHRYSALIDE_PLUGIN_MAGIC, \ + .abi_version = CURRENT_ABI_VERSION, \ + \ + .name = n, \ + .desc = d, \ + .version = v, \ + \ + r, \ + \ + a, \ + \ +} + + +/* Interfaçage primaire avec Chrysalide */ +#define DEFINE_CHRYSALIDE_ACTIVE_PLUGIN(n, d, v, ...) \ + DEFINE_CHRYSALIDE_PLUGIN(n, d, v, EMPTY_PG_LIST(.required), AL( __VA_ARGS__ )) + + + #endif /* _PLUGINS_PLUGIN_DEF_H */ diff --git a/src/plugins/plugin-int.h b/src/plugins/plugin-int.h index abe3995..de43a5c 100644 --- a/src/plugins/plugin-int.h +++ b/src/plugins/plugin-int.h @@ -33,6 +33,13 @@ +/* Exécute une action pendant un désassemblage de binaire. */ +typedef void (* pg_process_disassembly) (const GPluginModule *, PluginAction, GLoadedBinary *); + + + + + /* Précise le nom associé au greffon. */ typedef char * (* get_plugin_name_fc) (void); @@ -53,19 +60,24 @@ typedef MatchingFormatAction (* is_matching_fc) (const GPluginModule *, char **, typedef bool (* execute_action_on_binary_fc) (const GPluginModule *, GLoadedBinary *, PluginAction); /* Exécute une action relative à un débogueur. */ -typedef bool (* execute_on_debugger_fc) (const GPluginModule *, GBinaryDebugger *, PluginAction); +//typedef bool (* execute_on_debugger_fc) (const GPluginModule *, GBinaryDebugger *, PluginAction); -/* Greffon pour OpenIDA (instance) */ +/* Greffon pour Chrysalide (instance) */ struct _GPluginModule { GObject parent; /* A laisser en premier */ + char *filename; /* Fichier associé au greffon */ GModule *module; /* Abstration de manipulation */ - char *name; /* Nom associé au greffon */ - char *filename; /* Fichier associé au greffon */ - PluginType type; /* Type(s) du greffon */ + const plugin_interface *interface; /* Déclaration d'interfaçage */ + + + + + //char *name; /* Nom associé au greffon */ + //PluginType type; /* Type(s) du greffon */ init_plugin_fc init; /* Procédure d'initialisation */ exit_plugin_fc exit; /* Procédure d'extinction */ @@ -74,12 +86,15 @@ struct _GPluginModule is_matching_fc is_matching; /* Recherche de correspondance */ execute_action_on_binary_fc exec_on_bin;/* Action sur un binaire */ - execute_on_debugger_fc handle_debugger; /* Action liée à un débogueur */ + //execute_on_debugger_fc handle_debugger; /* Action liée à un débogueur */ + + + pg_process_disassembly proc_disass; /* Catégorie 'désassemblage' */ }; -/* Greffon pour OpenIDA (classe) */ +/* Greffon pour Chrysalide (classe) */ struct _GPluginModuleClass { GObjectClass parent; /* A laisser en premier */ diff --git a/src/plugins/plugin.c b/src/plugins/plugin.c index c43ac37..fd55f8c 100644 --- a/src/plugins/plugin.c +++ b/src/plugins/plugin.c @@ -137,10 +137,7 @@ static void g_plugin_module_dispose(GPluginModule *plugin) static void g_plugin_module_finalize(GPluginModule *plugin) { - if (plugin->name != NULL) - free(plugin->name); - if (plugin->filename != NULL) - free(plugin->filename); + free(plugin->filename); G_OBJECT_CLASS(g_plugin_module_parent_class)->finalize(G_OBJECT(plugin)); @@ -162,13 +159,16 @@ static void g_plugin_module_finalize(GPluginModule *plugin) GPluginModule *g_plugin_module_new(const gchar *filename) { GPluginModule *result; /* Structure à retourner */ - get_plugin_name_fc get_name; /* Nom du greffon */ - //get_plugin_action_fc __get_type; /* Type(s) de greffon */ - get_plugin_action_fc get_action; /* Actions du greffon */ + plugin_abi_version_t current; /* Version de l'ABI actuelle */ + size_t i; /* Boucle de parcours */ + uint32_t category; /* Catégorie principale */ + uint32_t sub; /* Sous-catégorie visée */ char *dir; /* Répertoire modifiable */ result = g_object_new(G_TYPE_PLUGIN_MODULE, NULL); + result->filename = strdup(filename); + result->module = g_module_open(filename, G_MODULE_BIND_LAZY); if (result->module == NULL) { @@ -178,58 +178,94 @@ GPluginModule *g_plugin_module_new(const gchar *filename) goto bad_plugin; } - if (!g_module_symbol(result->module, "get_plugin_name", (gpointer *)&get_name)) +#define load_plugin_symbol(mod, sym, dest) \ + ({ \ + bool __result; \ + if (!g_module_symbol(mod, sym, (gpointer *)dest)) \ + { \ + log_variadic_message(LMT_ERROR, \ + _("No '%s' entry in plugin candidate '%s'"), \ + sym, result->filename); \ + __result = false; \ + } \ + else __result = true; \ + __result; \ + }) + + + /* Récupération de la version d'ABI */ + + if (!load_plugin_symbol(result->module, "_chrysalide_plugin", &result->interface)) + goto bad_plugin; + + current = CURRENT_ABI_VERSION; + + if (0 /* check_version() */) { + log_variadic_message(LMT_ERROR, - _("No 'get_plugin_name' entry in plugin candidate '%s'"), + _("Bad version... '%s'"), filename); goto bad_plugin; + + } - result->name = get_name(); - result->filename = strdup(filename); + /* Localisation des différents points d'entrée déclarés */ - if (!g_module_symbol(result->module, "init_plugin", (gpointer *)&result->init)) - result->init = NULL; + for (i = 0; i < result->interface->actions_count; i++) + { + category = MASK_PLUGIN_CATEGORY(result->interface->actions[i]); + sub = MASK_PLUGIN_SUB_CATEGORY(result->interface->actions[i]); - if (!g_module_symbol(result->module, "exit_plugin", (gpointer *)&result->exit)) - result->exit = NULL; + printf(" GET cat = 0x%08x - sub = 0x%08x\n", category, sub); - /* + switch (category) + { + case DPC_NONE: + switch (sub) + { + case DPS_NONE: + break; - if (!g_module_symbol(result->module, "get_plugin_type", (gpointer *)&__get_type)) - { - printf("No 'get_plugin_type' symbol found in the plugin '%s'\n", filename); - goto bad_plugin; - } + default: + log_variadic_message(LMT_WARNING, + _("Unknown sub-category '0x%02x' in plugin '%s'..."), sub, filename); + break; - result->type = __get_type(); + } + break; - printf("Plugin type :: 0x%08x\n", result->type); - */ + case DPC_BINARY_PROCESSING: + switch (sub) + { - if (!g_module_symbol(result->module, "get_plugin_action", (gpointer *)&get_action)) - { - printf("Err plugin get_action sym\n"); - //g_object_destroy(result); - return NULL; - } - result->get_action = get_action; + case DPS_DISASSEMBLY: + if (!load_plugin_symbol(result->module, + "process_binary_disassembly", &result->proc_disass)) + goto bad_plugin; + break; - if (g_plugin_module_get_action(result) & (PGA_BINARY_ACTIONS | /* FIXME : supprimer le reste */ PGA_DISASSEMBLE | PGA_DISASS_PROCESS | PGA_CODE_PROCESS)) - { - if (!g_module_symbol(result->module, "execute_action_on_binary", (gpointer *)&result->exec_on_bin)) - { - printf("Err plugin disass sym\n"); - //g_object_destroy(result); - return NULL; - } + default: + log_variadic_message(LMT_WARNING, + _("Unknown sub-category '0x%02x' in plugin '%s'..."), sub, filename); + break; + + } + + break; + default: + log_variadic_message(LMT_WARNING, + _("Unknown category '0x%02x' in plugin '%s'..."), category, filename); + break; + + } } @@ -237,6 +273,19 @@ GPluginModule *g_plugin_module_new(const gchar *filename) + /* + if (!g_module_symbol(result->module, "init_plugin", (gpointer *)&result->init)) + result->init = NULL; + + if (!g_module_symbol(result->module, "exit_plugin", (gpointer *)&result->exit)) + result->exit = NULL; + */ + + + + + + /* Conclusion */ dir = strdup(filename); dir = dirname(dir); @@ -261,17 +310,17 @@ GPluginModule *g_plugin_module_new(const gchar *filename) * * * Paramètres : plugin = greffon à consulter. * * * -* Description : Fournit le nom associé au greffon. * +* Description : Fournit la description du greffon dans son intégralité. * * * -* Retour : Désignation humaine. * +* Retour : Interfaçage renseigné. * * * * Remarques : - * * * ******************************************************************************/ -const char *g_plugin_module_get_name(const GPluginModule *plugin) +const plugin_interface *g_plugin_module_get_interface(const GPluginModule *plugin) { - return plugin->name; + return plugin->interface; } @@ -294,12 +343,12 @@ void g_plugin_module_log_simple_message(const GPluginModule *plugin, LogMessageT size_t len; /* Taille tampon disponible */ char *buffer; /* Tampon du msg reconstitué */ - len = 1 + strlen(plugin->name) + 2 + strlen(msg) + 1; + len = 3 +1 + strlen(plugin->interface->name) + 3 + 2 + strlen(msg) + 1; buffer = calloc(len, sizeof(char)); - strcpy(buffer, "["); - strcat(buffer, plugin->name); - strcat(buffer, "] "); + strcpy(buffer, "["); + strcat(buffer, plugin->interface->name); + strcat(buffer, "] "); strcat(buffer, msg); log_simple_message(type, buffer); @@ -368,93 +417,23 @@ void g_plugin_module_log_variadic_message(const GPluginModule *plugin, LogMessag /****************************************************************************** * * -* Paramètres : plugin = greffon à consulter. * -* * -* Description : Indique les opérations offertes par un greffon donné. * -* * -* Retour : Action(s) offerte(s) par le greffon. * -* * -* Remarques : - * -* * -******************************************************************************/ - -PluginAction g_plugin_module_get_action(const GPluginModule *plugin) -{ - return plugin->get_action(plugin); - -} - - -/****************************************************************************** -* * -* Paramètres : plugin = greffon de prise en charge à utiliser. * -* filename = éventuel nom de fichier associé ou NULL. [OUT] * -* data = données chargées. [OUT] * -* length = quantité de ces données. [OUT] * -* * -* Description : Identifie un format à associer à un contenu binaire. * -* * -* Retour : Bilan de la recherche de correspondances. * -* * -* Remarques : - * -* * -******************************************************************************/ - -MatchingFormatAction g_plugin_module_is_matching(const GPluginModule *plugin, char **filename, bin_t **data, off_t *length) -{ - MatchingFormatAction result; /* Valeur à retourner */ - - if (plugin->is_matching == NULL) - return MFA_NONE; - - result = plugin->is_matching(plugin, filename, data, length); - - return result; - -} - - - - - - -/****************************************************************************** -* * -* Paramètres : plugin = greffon à consulter. * -* binary = binaire dont le contenu est à traiter. * -* action = action attendue. * +* Paramètres : plugin = greffon à manipuler. * +* action = type d'action attendue. * +* binary = binaire dont le contenu est en cours de traitement. * * * -* Description : Exécute une action définie sur un binaire chargé. * +* Description : Exécute une action pendant un désassemblage de binaire. * * * -* Retour : true si une action a été menée, false sinon. * +* Retour : - * * * * Remarques : - * * * ******************************************************************************/ -bool g_plugin_module_execute_action_on_binary(const GPluginModule *plugin, GLoadedBinary *binary, PluginAction action) +void g_plugin_module_process_disassembly_event(const GPluginModule *plugin, PluginAction action, GLoadedBinary *binary) { - return plugin->exec_on_bin(plugin, binary, action); - -} - - -/****************************************************************************** -* * -* Paramètres : plugin = greffon à consulter. * -* debugger = débogueur à l'origine de l'opération. * -* action = action attendue. * -* * -* Description : Exécute une action relative à un débogueur. * -* * -* Retour : true si une action a été menée, false sinon. * -* * -* Remarques : - * -* * -******************************************************************************/ + printf("plugin = %p\n", plugin); + printf("plugin->proc_disass = %p\n", plugin->proc_disass); -bool g_plugin_module_handle_debugger(const GPluginModule *plugin, GBinaryDebugger *debugger, PluginAction action) -{ - return plugin->handle_debugger(plugin, debugger, action); + plugin->proc_disass(plugin, action, binary); } diff --git a/src/plugins/plugin.h b/src/plugins/plugin.h index 01a523d..835b1c7 100644 --- a/src/plugins/plugin.h +++ b/src/plugins/plugin.h @@ -30,13 +30,14 @@ #include "plugin-def.h" +#include "../analysis/binary.h" -/* Greffon pour OpenIDA (instance) */ +/* Greffon pour Chrysalide (instance) */ typedef struct _GPluginModule GPluginModule; -/* Greffon pour OpenIDA (classe) */ +/* Greffon pour Chrysalide (classe) */ typedef struct _GPluginModuleClass GPluginModuleClass; @@ -54,22 +55,14 @@ GType g_plugin_module_get_type(void); /* Crée un module pour un greffon donné. */ GPluginModule *g_plugin_module_new(const gchar *); -/* Fournit le nom associé au greffon. */ -const char *g_plugin_module_get_name(const GPluginModule *); +/* Fournit la description du greffon dans son intégralité. */ +const plugin_interface *g_plugin_module_get_interface(const GPluginModule *); -/* Indique les opérations offertes par un greffon donné. */ -PluginAction g_plugin_module_get_action(const GPluginModule *); -/* Identifie un format à associer à un contenu binaire. */ -MatchingFormatAction g_plugin_module_is_matching(const GPluginModule *, char **, bin_t **, off_t *); - -/* Exécute une action définie sur un binaire chargé. */ -bool g_plugin_module_execute_action_on_binary(const GPluginModule *, GLoadedBinary *, PluginAction); - -/* Exécute une action relative à un débogueur. */ -bool g_plugin_module_handle_debugger(const GPluginModule *, GBinaryDebugger *, PluginAction); +/* Exécute une action pendant un désassemblage de binaire. */ +void g_plugin_module_process_disassembly_event(const GPluginModule *, PluginAction, GLoadedBinary *); -- cgit v0.11.2-87-g4458