[gnome-builder/wip/chergert/debugger: 60/100] gdb: port plugin to C



commit 7c63626669f58b032fd6168c2918059b6fe3f779
Author: Christian Hergert <chergert redhat com>
Date:   Sat Mar 25 19:43:48 2017 -0700

    gdb: port plugin to C
    
    Python was fine for some prototyping, but the G-I issues are slowing down
    my ability to iterate quickly. So I guess we'll just do this plugin in C.
    
    I was hoping to use this one as an example, but I guess we'll just do a
    mock debugger to show people how to do it.

 configure.ac                   |    2 +
 plugins/Makefile.am            |    1 +
 plugins/gdb/Makefile.am        |   29 ++++
 plugins/gdb/configure.ac       |   12 ++
 plugins/gdb/gbp-gdb-debugger.c |  357 ++++++++++++++++++++++++++++++++++++++++
 plugins/gdb/gbp-gdb-debugger.h |   32 ++++
 plugins/gdb/gbp-gdb-plugin.c   |   27 +++
 plugins/gdb/gdb.plugin         |    3 +-
 plugins/gdb/gdb_plugin.py      |  141 ----------------
 plugins/gdb/meson.build        |   23 +++
 plugins/meson.build            |    1 +
 11 files changed, 485 insertions(+), 143 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 7c1287e..187a653 100644
--- a/configure.ac
+++ b/configure.ac
@@ -308,6 +308,7 @@ m4_include([plugins/file-search/configure.ac])
 m4_include([plugins/flatpak/configure.ac])
 m4_include([plugins/fpaste/configure.ac])
 m4_include([plugins/gcc/configure.ac])
+m4_include([plugins/gdb/configure.ac])
 m4_include([plugins/gettext/configure.ac])
 m4_include([plugins/git/configure.ac])
 m4_include([plugins/gnome-code-assistance/configure.ac])
@@ -595,6 +596,7 @@ echo "  Editorconfig ......................... : ${enable_editorconfig}"
 echo "  Flatpak .............................. : ${enable_flatpak_plugin}"
 echo "  Fpaste.org ........................... : ${enable_fpaste_plugin}"
 echo "  GCC .................................. : ${enable_gcc_plugin}"
+echo "  GDB .................................. : ${enable_gdb_plugin}"
 echo "  Gettext .............................. : ${enable_gettext_plugin}"
 echo "  Git Version Control .................. : ${enable_git_plugin}"
 echo "  Global File Search ................... : ${enable_file_search_plugin}"
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 88326b7..16c3b77 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -17,6 +17,7 @@ SUBDIRS =                            \
        flatpak                      \
        fpaste                       \
        gcc                          \
+       gdb                          \
        gettext                      \
        git                          \
        gnome-code-assistance        \
diff --git a/plugins/gdb/Makefile.am b/plugins/gdb/Makefile.am
new file mode 100644
index 0000000..4489429
--- /dev/null
+++ b/plugins/gdb/Makefile.am
@@ -0,0 +1,29 @@
+if ENABLE_GDB_PLUGIN
+
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+plugin_LTLIBRARIES = libgdb-plugin.la
+dist_plugin_DATA = gdb.plugin
+
+libgdb_plugin_la_SOURCES = \
+       gbp-gdb-debugger.c \
+       gbp-gdb-debugger.h \
+       gbp-gdb-plugin.c   \
+       $(NULL)
+
+libgdb_plugin_la_CFLAGS =           \
+       $(PLUGIN_CFLAGS)            \
+       -I$(top_srcdir)/contrib/mi2 \
+       $(NULL)
+
+libgdb_plugin_la_LDFLAGS =                         \
+       $(PLUGIN_LDFLAGS)                          \
+       $(top_builddir)/contrib/mi2/libmi2-glib.la \
+       $(NULL)
+
+include $(top_srcdir)/plugins/Makefile.plugin
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/gdb/configure.ac b/plugins/gdb/configure.ac
new file mode 100644
index 0000000..415a4e5
--- /dev/null
+++ b/plugins/gdb/configure.ac
@@ -0,0 +1,12 @@
+# --enable-gdb-plugin=yes/no
+AC_ARG_ENABLE([gdb-plugin],
+              [AS_HELP_STRING([--enable-gdb-plugin=@<:@yes/no@:>@],
+                              [Build with support for searching files in global search.])],
+              [enable_gdb_plugin=$enableval],
+              [enable_gdb_plugin=yes])
+
+# for if ENABLE_GDB_PLUGIN in Makefile.am
+AM_CONDITIONAL(ENABLE_GDB_PLUGIN, test x$enable_gdb_plugin != xno)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/gdb/Makefile])
diff --git a/plugins/gdb/gbp-gdb-debugger.c b/plugins/gdb/gbp-gdb-debugger.c
new file mode 100644
index 0000000..001992b
--- /dev/null
+++ b/plugins/gdb/gbp-gdb-debugger.c
@@ -0,0 +1,357 @@
+/* gbp-gdb-debugger.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "gbp-gdb-plugin"
+
+#include <glib/gi18n.h>
+#include <egg-signal-group.h>
+#include <mi2-glib.h>
+
+#include "gbp-gdb-debugger.h"
+
+struct _GbpGdbDebugger
+{
+  IdeObject       parent;
+  Mi2Client      *client;
+  EggSignalGroup *client_signals;
+  IdeRunner      *runner;
+  EggSignalGroup *runner_signals;
+  gint            mapped_fd;
+};
+
+enum {
+  PROP_0,
+  PROP_CAN_STEP_IN,
+  PROP_CAN_STEP_OVER,
+  PROP_CAN_CONTINUE,
+  N_PROPS
+};
+
+/* Globals */
+static GParamSpec *properties [N_PROPS];
+
+/* Forward Declarations */
+static void      debugger_iface_init                            (IdeDebuggerInterface *iface);
+static void      gbp_gdb_debugger_finalize                      (GObject              *object);
+static gchar    *gbp_gdb_debugger_get_name                      (IdeDebugger          *debugger);
+static void      gbp_gdb_debugger_prepare                       (IdeDebugger          *debugger,
+                                                                 IdeRunner            *runner);
+static gboolean  gbp_gdb_debugger_supports_runner               (IdeDebugger          *debugger,
+                                                                 IdeRunner            *runner,
+                                                                 gint                 *priority);
+static void      gbp_gdb_debugger_on_runner_spawned             (GbpGdbDebugger       *self,
+                                                                 const gchar          *identifier,
+                                                                 IdeRunner            *runner);
+static void      gbp_gdb_debugger_on_runner_exited              (GbpGdbDebugger       *self,
+                                                                 IdeRunner            *runner);
+static void      gbp_gdb_debugger_on_client_breakpoint_inserted (GbpGdbDebugger       *self,
+                                                                 Mi2Breakpoint        *breakpoint,
+                                                                 Mi2Client            *client);
+static void      gbp_gdb_debugger_on_client_breakpoint_removed  (GbpGdbDebugger       *self,
+                                                                 gint                  breakpoint_id,
+                                                                 Mi2Client            *client);
+static void      gbp_gdb_debugger_on_client_event               (GbpGdbDebugger       *self,
+                                                                 Mi2EventMessage      *message,
+                                                                 Mi2Client            *client);
+static void      gbp_gdb_debugger_on_client_stopped             (GbpGdbDebugger       *self,
+                                                                 Mi2StopReason         reason,
+                                                                 Mi2EventMessage      *message,
+                                                                 Mi2Client            *client);
+static void      gbp_gdb_debugger_on_client_log                 (GbpGdbDebugger       *self,
+                                                                 const gchar          *message,
+                                                                 Mi2Client            *client);
+
+/* Type initialization */
+G_DEFINE_TYPE_WITH_CODE (GbpGdbDebugger, gbp_gdb_debugger, IDE_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_DEBUGGER, debugger_iface_init))
+
+static gchar *
+gbp_gdb_debugger_get_name (IdeDebugger *debugger)
+{
+  g_assert (GBP_IS_GDB_DEBUGGER (debugger));
+
+  return g_strdup (_("GNU Debugger"));
+}
+
+static gboolean
+gbp_gdb_debugger_supports_runner (IdeDebugger *debugger,
+                                  IdeRunner   *runner,
+                                  gint        *priority)
+{
+  IdeRuntime *runtime;
+
+  g_assert (IDE_IS_DEBUGGER (debugger));
+  g_assert (IDE_IS_RUNNER (runner));
+  g_assert (priority != NULL);
+
+  runtime = ide_runner_get_runtime (runner);
+
+  if (ide_runtime_contains_program_in_path (runtime, "gdb", NULL))
+    {
+      *priority = G_MAXINT;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+gbp_gdb_debugger_prepare (IdeDebugger *debugger,
+                          IdeRunner   *runner)
+{
+  static gchar *prepend_argv[] = { "gdb", "--interpreter", "mi2", "--args" };
+  GbpGdbDebugger *self = (GbpGdbDebugger *)debugger;
+  int tty_fd;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_GDB_DEBUGGER (self));
+  g_assert (IDE_IS_RUNNER (runner));
+
+  /* Prepend arguments in reverse to preserve ordering */
+  for (guint i = G_N_ELEMENTS (prepend_argv); i > 0; i--)
+    ide_runner_prepend_argv (runner, prepend_argv[i-1]);
+
+  /* Connect to all our important signals */
+  egg_signal_group_set_target (self->runner_signals, runner);
+
+  /*
+   * We steal and remap the PTY fd into the process so that gdb does not get
+   * the controlling terminal, but instead allow us to ask gdb to setup the
+   * inferrior with that same PTY.
+   */
+  if (-1 != (tty_fd = ide_runner_steal_tty (runner)))
+    self->mapped_fd = ide_runner_take_fd (runner, tty_fd, -1);
+
+  /* We need access to stdin/stdout for communicating with gdb */
+  ide_runner_set_flags (runner, G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE);
+
+  IDE_EXIT;
+}
+
+static void
+debugger_iface_init (IdeDebuggerInterface *iface)
+{
+  iface->get_name        = gbp_gdb_debugger_get_name;
+  iface->prepare         = gbp_gdb_debugger_prepare;
+  iface->supports_runner = gbp_gdb_debugger_supports_runner;
+}
+
+static void
+gbp_gdb_debugger_on_runner_spawned (GbpGdbDebugger *self,
+                                    const gchar    *identifier,
+                                    IdeRunner      *runner)
+{
+  g_autoptr(GIOStream) io_stream = NULL;
+  g_autofree gchar *inferior_command = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_GDB_DEBUGGER (self));
+  g_assert (self->client == NULL);
+  g_assert (identifier != NULL);
+  g_assert (IDE_IS_RUNNER (runner));
+
+  /* Create an IOStream to track pipe communication with gdb */
+  io_stream = g_simple_io_stream_new (ide_runner_get_stdout (runner),
+                                      ide_runner_get_stdin (runner));
+
+  /* Setup our mi2 client to RPC to gdb */
+  self->client = mi2_client_new (io_stream);
+
+  /* Connect to all our signals up front necessary to control gdb */
+  egg_signal_group_set_target (self->client_signals, self->client);
+
+  /* Now ask the mi2 client to start procesing data */
+  mi2_client_listen_async (self->client, NULL, NULL, NULL);
+
+  /* Ask gdb to use our mapped in FD for the TTY when spawning the child */
+  inferior_command = g_strdup_printf ("-gdb-set inferrior-tty /proc/self/fd/%d", self->mapped_fd);
+  mi2_client_exec_async (self->client, inferior_command, NULL, NULL, NULL);
+
+  /* Now ask gdb to start running the program */
+  mi2_client_run_async (self->client, NULL, NULL, NULL);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_gdb_debugger_on_runner_exited (GbpGdbDebugger *self,
+                                   IdeRunner      *runner)
+{
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_GDB_DEBUGGER (self));
+  g_assert (IDE_IS_RUNNER (runner));
+
+  IDE_EXIT;
+}
+
+static void
+gbp_gdb_debugger_on_client_breakpoint_inserted (GbpGdbDebugger *self,
+                                                Mi2Breakpoint  *breakpoint,
+                                                Mi2Client      *client)
+{
+  g_assert (GBP_IS_GDB_DEBUGGER (self));
+  g_assert (MI2_IS_BREAKPOINT (breakpoint));
+  g_assert (MI2_IS_CLIENT (client));
+
+}
+
+static void
+gbp_gdb_debugger_on_client_breakpoint_removed (GbpGdbDebugger *self,
+                                               gint            breakpoint_id,
+                                               Mi2Client      *client)
+{
+  g_assert (GBP_IS_GDB_DEBUGGER (self));
+  g_assert (breakpoint_id > 0);
+  g_assert (MI2_IS_CLIENT (client));
+
+}
+
+static void
+gbp_gdb_debugger_on_client_event (GbpGdbDebugger  *self,
+                                  Mi2EventMessage *message,
+                                  Mi2Client       *client)
+{
+  g_assert (GBP_IS_GDB_DEBUGGER (self));
+  g_assert (MI2_IS_EVENT_MESSAGE (message));
+  g_assert (MI2_IS_CLIENT (client));
+
+}
+
+static void
+gbp_gdb_debugger_on_client_stopped (GbpGdbDebugger  *self,
+                                    Mi2StopReason    reason,
+                                    Mi2EventMessage *message,
+                                    Mi2Client       *client)
+{
+  g_assert (GBP_IS_GDB_DEBUGGER (self));
+  g_assert (MI2_IS_EVENT_MESSAGE (message));
+  g_assert (MI2_IS_CLIENT (client));
+
+}
+
+static void
+gbp_gdb_debugger_on_client_log (GbpGdbDebugger *self,
+                                const gchar    *message,
+                                Mi2Client      *client)
+{
+  g_assert (GBP_IS_GDB_DEBUGGER (self));
+  g_assert (message != NULL);
+  g_assert (MI2_IS_CLIENT (client));
+
+}
+
+static void
+gbp_gdb_debugger_finalize (GObject *object)
+{
+  GbpGdbDebugger *self = (GbpGdbDebugger *)object;
+
+  g_clear_object (&self->client_signals);
+  g_clear_object (&self->client);
+  g_clear_object (&self->runner_signals);
+  g_clear_object (&self->runner);
+
+  G_OBJECT_CLASS (gbp_gdb_debugger_parent_class)->finalize (object);
+}
+
+static void
+gbp_gdb_debugger_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  switch (prop_id)
+    {
+    case PROP_CAN_STEP_IN:
+    case PROP_CAN_STEP_OVER:
+    case PROP_CAN_CONTINUE:
+      g_value_set_boolean (value, FALSE);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gdb_debugger_class_init (GbpGdbDebuggerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_gdb_debugger_finalize;
+  object_class->get_property = gbp_gdb_debugger_get_property;
+
+#define REGISTER_BOOLEAN(NAME, name) \
+  properties [NAME] = g_param_spec_boolean (name, NULL, NULL, FALSE, \
+                                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)
+
+  REGISTER_BOOLEAN (PROP_CAN_STEP_IN, "can-step-in");
+  REGISTER_BOOLEAN (PROP_CAN_STEP_OVER, "can-step-over");
+  REGISTER_BOOLEAN (PROP_CAN_CONTINUE, "can-continue");
+
+#undef REGISTER_BOOLEAN
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_gdb_debugger_init (GbpGdbDebugger *self)
+{
+  self->runner_signals = egg_signal_group_new (IDE_TYPE_RUNNER);
+
+  egg_signal_group_connect_object (self->runner_signals,
+                                   "spawned",
+                                   G_CALLBACK (gbp_gdb_debugger_on_runner_spawned),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+  egg_signal_group_connect_object (self->runner_signals,
+                                   "exited",
+                                   G_CALLBACK (gbp_gdb_debugger_on_runner_exited),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  self->client_signals = egg_signal_group_new (MI2_TYPE_CLIENT);
+
+  egg_signal_group_connect_object (self->runner_signals,
+                                   "breakpoint-inserted",
+                                   G_CALLBACK (gbp_gdb_debugger_on_client_breakpoint_inserted),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+  egg_signal_group_connect_object (self->runner_signals,
+                                   "breakpoint-removed",
+                                   G_CALLBACK (gbp_gdb_debugger_on_client_breakpoint_removed),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+  egg_signal_group_connect_object (self->runner_signals,
+                                   "event",
+                                   G_CALLBACK (gbp_gdb_debugger_on_client_event),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+  egg_signal_group_connect_object (self->runner_signals,
+                                   "stopped",
+                                   G_CALLBACK (gbp_gdb_debugger_on_client_stopped),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+  egg_signal_group_connect_object (self->runner_signals,
+                                   "log",
+                                   G_CALLBACK (gbp_gdb_debugger_on_client_log),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+}
diff --git a/plugins/gdb/gbp-gdb-debugger.h b/plugins/gdb/gbp-gdb-debugger.h
new file mode 100644
index 0000000..3be86d7
--- /dev/null
+++ b/plugins/gdb/gbp-gdb-debugger.h
@@ -0,0 +1,32 @@
+/* gbp-gdb-debugger.h
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GBP_GDB_DEBUGGER_H
+#define GBP_GDB_DEBUGGER_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_GDB_DEBUGGER (gbp_gdb_debugger_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGdbDebugger, gbp_gdb_debugger, GBP, GDB_DEBUGGER, IdeObject)
+
+G_END_DECLS
+
+#endif /* GBP_GDB_DEBUGGER_H */
diff --git a/plugins/gdb/gbp-gdb-plugin.c b/plugins/gdb/gbp-gdb-plugin.c
new file mode 100644
index 0000000..8a721e4
--- /dev/null
+++ b/plugins/gdb/gbp-gdb-plugin.c
@@ -0,0 +1,27 @@
+/* gbp-gdb-plugin.c
+ *
+ * Copyright (C) 2017 Christian Hergert <chergert redhat com>
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ide.h>
+
+#include "gbp-gdb-debugger.h"
+
+void
+peas_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module, IDE_TYPE_DEBUGGER, GBP_TYPE_GDB_DEBUGGER);
+}
diff --git a/plugins/gdb/gdb.plugin b/plugins/gdb/gdb.plugin
index 50aa598..96aea9d 100644
--- a/plugins/gdb/gdb.plugin
+++ b/plugins/gdb/gdb.plugin
@@ -1,6 +1,5 @@
 [Plugin]
-Module=gdb_plugin
-Loader=python3
+Module=gdb-plugin
 Name=Gdb
 Description=Provides integration with the GNU Debugger
 Authors=Christian Hergert <chergert redhat com>
diff --git a/plugins/gdb/meson.build b/plugins/gdb/meson.build
new file mode 100644
index 0000000..b494068
--- /dev/null
+++ b/plugins/gdb/meson.build
@@ -0,0 +1,23 @@
+if get_option('with_gdb')
+
+gdb_sources = [
+  'gbp-gdb-debugger.c',
+  'gbp-gdb-debugger.h',
+  'gbp-gdb-plugin.c',
+]
+
+gdb_deps = plugin_deps + [
+  libmi2_glib_dep,
+]
+
+shared_module('gdb-plugin', gdb_sources,
+  dependencies: gdb_deps,
+  link_args: plugin_link_args,
+  link_depends: plugin_link_deps,
+  install: true,
+  install_dir: plugindir,
+)
+
+install_data('gdb.plugin', install_dir: plugindir)
+
+endif
diff --git a/plugins/meson.build b/plugins/meson.build
index 0da6c26..0028d92 100644
--- a/plugins/meson.build
+++ b/plugins/meson.build
@@ -28,6 +28,7 @@ subdir('file-search')
 subdir('flatpak')
 subdir('fpaste')
 subdir('gcc')
+subdir('gdb')
 subdir('gettext')
 subdir('git')
 subdir('gnome-code-assistance')


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]