[gnome-builder/wip/chergert/bug1: 2/4] debugger: add IdeDebugger API



commit c49673f9db729b56f0f0a30b8f66bb4395421085
Author: Christian Hergert <chergert redhat com>
Date:   Thu Aug 24 19:17:46 2017 -0700

    debugger: add IdeDebugger API
    
    This provides a somewhat simplistic API abstracting debugger communication.
    A reference implementation will be provided using GDB.
    
    There are some things still missing, but we can add that as we go. In
    particular, we don't yet have a way to dive into complex variables and
    tracking variable changes across runs (you have to requery, which is
    potentially slow for various slow hardware).
    
    We still need to get the UI in place, but this has some reusable components
    that can be used in the debugger perspective.

 libide/debugger/ide-debugger-actions.c          |  205 +++
 libide/debugger/ide-debugger-address-map.c      |  222 +++
 libide/debugger/ide-debugger-address-map.h      |   55 +
 libide/debugger/ide-debugger-breakpoint.c       |  908 +++++++++++
 libide/debugger/ide-debugger-breakpoint.h       |   81 +
 libide/debugger/ide-debugger-breakpoints-view.c |  553 +++++++
 libide/debugger/ide-debugger-breakpoints-view.h |   36 +
 libide/debugger/ide-debugger-fallbacks.c        |  256 +++
 libide/debugger/ide-debugger-frame.c            |  399 +++++
 libide/debugger/ide-debugger-frame.h            |   66 +
 libide/debugger/ide-debugger-instruction.c      |  215 +++
 libide/debugger/ide-debugger-instruction.h      |   48 +
 libide/debugger/ide-debugger-libraries-view.c   |  363 +++++
 libide/debugger/ide-debugger-libraries-view.h   |   36 +
 libide/debugger/ide-debugger-library.c          |  275 ++++
 libide/debugger/ide-debugger-library.h          |   59 +
 libide/debugger/ide-debugger-locals-view.c      |  433 +++++
 libide/debugger/ide-debugger-locals-view.h      |   45 +
 libide/debugger/ide-debugger-private.h          |   95 ++
 libide/debugger/ide-debugger-register.c         |  233 +++
 libide/debugger/ide-debugger-register.h         |   54 +
 libide/debugger/ide-debugger-registers-view.c   |  326 ++++
 libide/debugger/ide-debugger-registers-view.h   |   36 +
 libide/debugger/ide-debugger-thread-group.c     |  229 +++
 libide/debugger/ide-debugger-thread-group.h     |   50 +
 libide/debugger/ide-debugger-thread.c           |  192 +++
 libide/debugger/ide-debugger-thread.h           |   47 +
 libide/debugger/ide-debugger-threads-view.c     |  820 ++++++++++
 libide/debugger/ide-debugger-threads-view.h     |   35 +
 libide/debugger/ide-debugger-types.c            |  189 +++
 libide/debugger/ide-debugger-types.h            |  196 +++
 libide/debugger/ide-debugger-variable.c         |  261 +++
 libide/debugger/ide-debugger-variable.h         |   55 +
 libide/debugger/ide-debugger.c                  | 1948 +++++++++++++++++++++++
 libide/debugger/ide-debugger.h                  |  354 ++++
 libide/ide.h                                    |   10 +
 libide/meson.build                              |   35 +
 37 files changed, 9420 insertions(+), 0 deletions(-)
---
diff --git a/libide/debugger/ide-debugger-actions.c b/libide/debugger/ide-debugger-actions.c
new file mode 100644
index 0000000..8b1f5ec
--- /dev/null
+++ b/libide/debugger/ide-debugger-actions.c
@@ -0,0 +1,205 @@
+/* ide-debugger-actions.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 "ide-debugger-actions"
+
+#include "ide-debugger-private.h"
+
+typedef struct _IdeDebuggerActionEntry IdeDebuggerActionEntry;
+
+typedef void (*IdeDebuggerActionHandler) (IdeDebugger                  *debugger,
+                                          const IdeDebuggerActionEntry *entry,
+                                          GVariant                     *param);
+
+struct _IdeDebuggerActionEntry
+{
+  const gchar              *action_name;
+  IdeDebuggerActionHandler  handler;
+  gint                      movement;
+  gint                      running_state;
+};
+
+enum {
+  RUNNING_STARTED     = 1,
+  RUNNING_NOT_STARTED = 1 << 1,
+  RUNNING_ACTIVE      = 1 << 2,
+  RUNNING_NOT_ACTIVE  = 1 << 3,
+};
+
+static gboolean
+check_running_state (IdeDebugger *self,
+                     gint         state)
+{
+  if (state & RUNNING_STARTED)
+    {
+      if (!_ide_debugger_get_has_started (self))
+        return FALSE;
+    }
+
+  if (state & RUNNING_NOT_STARTED)
+    {
+      if (_ide_debugger_get_has_started (self))
+        return FALSE;
+    }
+
+  if (state & RUNNING_ACTIVE)
+    {
+      if (!ide_debugger_get_is_running (self))
+        return FALSE;
+    }
+
+  if (state & RUNNING_NOT_ACTIVE)
+    {
+      if (ide_debugger_get_is_running (self))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+ide_debugger_actions_movement (IdeDebugger                  *self,
+                               const IdeDebuggerActionEntry *entry,
+                               GVariant                     *param)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (entry != NULL);
+
+  ide_debugger_move_async (self, entry->movement, NULL, NULL, NULL);
+}
+
+static void
+ide_debugger_actions_stop (IdeDebugger                  *self,
+                           const IdeDebuggerActionEntry *entry,
+                           GVariant                     *param)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (entry != NULL);
+
+  ide_debugger_interrupt_async (self, NULL, NULL, NULL, NULL);
+}
+
+static IdeDebuggerActionEntry action_info[] = {
+  { "start",     ide_debugger_actions_movement, IDE_DEBUGGER_MOVEMENT_START,     RUNNING_NOT_STARTED },
+  { "stop",      ide_debugger_actions_stop,     -1,                              RUNNING_STARTED | 
RUNNING_ACTIVE },
+  { "continue",  ide_debugger_actions_movement, IDE_DEBUGGER_MOVEMENT_CONTINUE,  RUNNING_STARTED | 
RUNNING_NOT_ACTIVE },
+  { "step-in",   ide_debugger_actions_movement, IDE_DEBUGGER_MOVEMENT_STEP_IN,   RUNNING_STARTED | 
RUNNING_NOT_ACTIVE },
+  { "step-over", ide_debugger_actions_movement, IDE_DEBUGGER_MOVEMENT_STEP_OVER, RUNNING_STARTED | 
RUNNING_NOT_ACTIVE },
+};
+
+static gboolean
+ide_debugger_get_action_enabled (GActionGroup *group,
+                                 const gchar  *action_name)
+{
+  IdeDebugger *self = IDE_DEBUGGER (group);
+
+  for (guint i = 0; i < G_N_ELEMENTS (action_info); i++)
+    {
+      const IdeDebuggerActionEntry *entry = &action_info[i];
+      if (g_strcmp0 (entry->action_name, action_name) == 0)
+        return check_running_state (self, entry->running_state);
+    }
+
+  return FALSE;
+}
+
+void
+_ide_debugger_update_actions (IdeDebugger *self)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+
+  for (guint i = 0; i < G_N_ELEMENTS (action_info); i++)
+    {
+      const IdeDebuggerActionEntry *entry = &action_info[i];
+      gboolean enabled;
+
+      enabled = ide_debugger_get_action_enabled (G_ACTION_GROUP (self), entry->action_name);
+      g_action_group_action_enabled_changed (G_ACTION_GROUP (self), entry->action_name, enabled);
+    }
+}
+
+static gboolean
+ide_debugger_has_action (GActionGroup *group,
+                         const gchar  *action_name)
+{
+  g_assert (IDE_IS_DEBUGGER (group));
+  g_assert (action_name != NULL);
+
+  for (guint i = 0; i < G_N_ELEMENTS (action_info); i++)
+    {
+      const IdeDebuggerActionEntry *entry = &action_info[i];
+
+      if (g_strcmp0 (action_name, entry->action_name) == 0)
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gchar **
+ide_debugger_list_actions (GActionGroup *group)
+{
+  GPtrArray *ar = g_ptr_array_new ();
+
+  for (guint i = 0; i < G_N_ELEMENTS (action_info); i++)
+    g_ptr_array_add (ar, g_strdup (action_info[i].action_name));
+  g_ptr_array_add (ar, NULL);
+
+  return (gchar **)g_ptr_array_free (ar, FALSE);
+}
+
+static gpointer
+null_return_func (void)
+{
+  return NULL;
+}
+
+static void
+ide_debugger_activate_action (GActionGroup *group,
+                              const gchar  *action_name,
+                              GVariant     *parameter)
+{
+  IdeDebugger *self = IDE_DEBUGGER (group);
+
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (action_name != NULL);
+
+  for (guint i = 0; i < G_N_ELEMENTS (action_info); i++)
+    {
+      const IdeDebuggerActionEntry *entry = &action_info[i];
+
+      if (g_strcmp0 (entry->action_name, action_name) == 0)
+        {
+          entry->handler (self, entry, parameter);
+          break;
+        }
+    }
+}
+
+void
+_ide_debugger_class_init_actions (GActionGroupInterface *iface)
+{
+  iface->has_action = ide_debugger_has_action;
+  iface->list_actions = ide_debugger_list_actions;
+  iface->get_action_enabled = ide_debugger_get_action_enabled;
+  iface->activate_action = ide_debugger_activate_action;
+  iface->get_action_parameter_type = (gpointer)null_return_func;
+  iface->get_action_state_type = (gpointer)null_return_func;
+  iface->get_action_state_hint = (gpointer)null_return_func;
+  iface->get_action_state = (gpointer)null_return_func;
+}
diff --git a/libide/debugger/ide-debugger-address-map.c b/libide/debugger/ide-debugger-address-map.c
new file mode 100644
index 0000000..a7797b5
--- /dev/null
+++ b/libide/debugger/ide-debugger-address-map.c
@@ -0,0 +1,222 @@
+/* ide-debugger-address-map.c
+ *
+ * Copyright (C) 2016-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 "ide-debugger-address-map"
+
+#include "ide-debugger-address-map.h"
+
+struct _IdeDebuggerAddressMap
+{
+  GSequence    *seq;
+  GStringChunk *chunk;
+};
+
+static inline gint
+uint64cmp (guint64 a,
+           guint64 b)
+{
+  if (a < b)
+    return -1;
+  else if (a > b)
+    return 1;
+  else
+    return 0;
+}
+
+static gint
+ide_debugger_address_map_entry_compare (gconstpointer a,
+                                        gconstpointer b,
+                                        gpointer      user_data)
+{
+  const IdeDebuggerAddressMapEntry *entry_a = a;
+  const IdeDebuggerAddressMapEntry *entry_b = b;
+
+  return uint64cmp (entry_a->start, entry_b->start);
+}
+
+static gint
+ide_debugger_address_map_entry_compare_in_range (gconstpointer a,
+                                                 gconstpointer b,
+                                                 gpointer      user_data)
+{
+  const IdeDebuggerAddressMapEntry *entry_a = a;
+  const IdeDebuggerAddressMapEntry *entry_b = b;
+
+  /*
+   * entry_b is the needle for the search.
+   * Only entry_b->start is set.
+   */
+
+  if ((entry_b->start >= entry_a->start) && (entry_b->start < entry_a->end))
+    return 0;
+
+  return uint64cmp (entry_a->start, entry_b->start);
+}
+
+static void
+ide_debugger_address_map_entry_free (gpointer data)
+{
+  IdeDebuggerAddressMapEntry *entry = data;
+
+  if (entry != NULL)
+    g_slice_free (IdeDebuggerAddressMapEntry, entry);
+}
+
+/**
+ * ide_debugger_address_map:
+ *
+ * Creates a new #IdeDebuggerAddressMap.
+ *
+ * The map is used to track the locations of mapped files in the inferriors
+ * address space. This allows relatively quick lookup to determine what file
+ * contains a given execution address (instruction pointer, etc).
+ *
+ * See also: ide_debugger_address_map_free()
+ *
+ * Returns: (transfer full): A new #IdeDebuggerAddressMap
+ *
+ * Since: 3.26
+ */
+IdeDebuggerAddressMap *
+ide_debugger_address_map_new (void)
+{
+  IdeDebuggerAddressMap *ret;
+
+  ret = g_slice_new (IdeDebuggerAddressMap);
+  ret->seq = g_sequence_new (ide_debugger_address_map_entry_free);
+  ret->chunk = g_string_chunk_new (4096);
+
+  return ret;
+}
+
+/**
+ * ide_debugger_address_map_free:
+ * @self: a #IdeDebuggerAddressMap
+ *
+ * Frees all memory associated with @self.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_address_map_free (IdeDebuggerAddressMap *self)
+{
+  if (self != NULL)
+    {
+      g_sequence_free (self->seq);
+      g_string_chunk_free (self->chunk);
+      g_slice_free (IdeDebuggerAddressMap, self);
+    }
+}
+
+/**
+ * ide_debugger_address_map_insert:
+ * @self: a #IdeDebuggerAddressMap
+ * @map: the map entry to insert
+ *
+ * Inserts a new map entry as specified by @entry.
+ *
+ * The contents of @entry are copied and therefore do not need to be kept
+ * around after calling this function.
+ *
+ * See also: ide_debugger_address_map_remove()
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_address_map_insert (IdeDebuggerAddressMap            *self,
+                                 const IdeDebuggerAddressMapEntry *entry)
+{
+  IdeDebuggerAddressMapEntry real = { 0 };
+
+  g_return_if_fail (self != NULL);
+  g_return_if_fail (entry != NULL);
+
+  real.filename = g_string_chunk_insert_const (self->chunk, entry->filename);
+  real.start = entry->start;
+  real.end = entry->end;
+  real.offset = entry->offset;
+
+  g_sequence_insert_sorted (self->seq,
+                            g_slice_dup (IdeDebuggerAddressMapEntry, &real),
+                            ide_debugger_address_map_entry_compare,
+                            NULL);
+}
+
+/**
+ * ide_debugger_address_map_lookup:
+ * @self: a #IdeDebuggerAddressMap
+ * @address: an address to locate the containing map
+ *
+ * Attempts to locate which #IdeDebuggerAddressMapEntry contains @address within
+ * the region specified by #IdeDebuggerAddressMapEntry.start and
+ * #IdeDebuggerAddressMapEntry.end.
+ *
+ * Returns: (nullable): An #IdeDebuggerAddressMapEntry or %NULL
+ *
+ * Since: 3.26
+ */
+const IdeDebuggerAddressMapEntry *
+ide_debugger_address_map_lookup (const IdeDebuggerAddressMap *self,
+                                 guint64                      address)
+{
+  IdeDebuggerAddressMapEntry entry = { NULL, 0, address, 0 };
+  GSequenceIter *iter;
+
+  g_return_val_if_fail (self != NULL, NULL);
+
+  iter = g_sequence_lookup (self->seq,
+                            &entry,
+                            ide_debugger_address_map_entry_compare_in_range,
+                            NULL);
+
+  if (iter == NULL || g_sequence_iter_is_end (iter))
+    return NULL;
+
+  return g_sequence_get (iter);
+}
+
+/**
+ * ide_debugger_address_map_remove:
+ * @self: a #IdeDebuggerAddressMap
+ * @address: the address contained in the map
+ *
+ * Removes the entry found containing @address.
+ *
+ * Since: 3.26
+ */
+gboolean
+ide_debugger_address_map_remove (IdeDebuggerAddressMap *self,
+                                 IdeDebuggerAddress     address)
+{
+  IdeDebuggerAddressMapEntry entry = { NULL, 0, address, 0 };
+  GSequenceIter *iter;
+
+  g_return_val_if_fail (self != NULL, FALSE);
+
+  iter = g_sequence_lookup (self->seq,
+                            &entry,
+                            ide_debugger_address_map_entry_compare_in_range,
+                            NULL);
+
+  if (iter == NULL || g_sequence_iter_is_end (iter))
+    return FALSE;
+
+  g_sequence_remove (iter);
+
+  return TRUE;
+}
diff --git a/libide/debugger/ide-debugger-address-map.h b/libide/debugger/ide-debugger-address-map.h
new file mode 100644
index 0000000..bd96bab
--- /dev/null
+++ b/libide/debugger/ide-debugger-address-map.h
@@ -0,0 +1,55 @@
+/* ide-debugger-address-map.h
+ *
+ * Copyright (C) 2016-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/>.
+ */
+
+#pragma once
+
+#include "ide-debugger-types.h"
+
+G_BEGIN_DECLS
+
+typedef struct _IdeDebuggerAddressMap IdeDebuggerAddressMap;
+
+typedef struct
+{
+  /*
+   * The file on disk that is mapped and the offset within the file.
+   */
+  const gchar *filename;
+  guint64 offset;
+
+  /*
+   * The range within the processes address space. We only support up to 64-bit
+   * address space for local and remote debugging.
+   */
+  IdeDebuggerAddress start;
+  IdeDebuggerAddress end;
+
+} IdeDebuggerAddressMapEntry;
+
+IdeDebuggerAddressMap            *ide_debugger_address_map_new    (void);
+void                              ide_debugger_address_map_insert (IdeDebuggerAddressMap            *self,
+                                                                   const IdeDebuggerAddressMapEntry *entry);
+gboolean                          ide_debugger_address_map_remove (IdeDebuggerAddressMap            *self,
+                                                                   IdeDebuggerAddress                
address);
+const IdeDebuggerAddressMapEntry *ide_debugger_address_map_lookup (const IdeDebuggerAddressMap      *self,
+                                                                   IdeDebuggerAddress                
address);
+void                              ide_debugger_address_map_free   (IdeDebuggerAddressMap            *self);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeDebuggerAddressMap, ide_debugger_address_map_free)
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-breakpoint.c b/libide/debugger/ide-debugger-breakpoint.c
new file mode 100644
index 0000000..b1b9b95
--- /dev/null
+++ b/libide/debugger/ide-debugger-breakpoint.c
@@ -0,0 +1,908 @@
+/* ide-debugger-breakpoint.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 "ide-debugger-breakpoint"
+
+#include "ide-debugger-breakpoint.h"
+#include "ide-debugger-types.h"
+
+typedef struct
+{
+  gchar                  *address;
+  gchar                  *function;
+  gchar                  *id;
+  gchar                  *file;
+  gchar                  *spec;
+  gchar                  *thread;
+
+  guint                   line;
+
+  IdeDebuggerDisposition  disposition : 8;
+  IdeDebuggerBreakMode    mode : 8;
+  guint                   enabled : 1;
+
+  gint64                  count;
+} IdeDebuggerBreakpointPrivate;
+
+enum {
+  PROP_0,
+  PROP_ADDRESS,
+  PROP_COUNT,
+  PROP_DISPOSITION,
+  PROP_ENABLED,
+  PROP_FILE,
+  PROP_FUNCTION,
+  PROP_ID,
+  PROP_LINE,
+  PROP_MODE,
+  PROP_SPEC,
+  PROP_THREAD,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeDebuggerBreakpoint, ide_debugger_breakpoint, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_debugger_breakpoint_finalize (GObject *object)
+{
+  IdeDebuggerBreakpoint *self = (IdeDebuggerBreakpoint *)object;
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_clear_pointer (&priv->address, g_free);
+  g_clear_pointer (&priv->function, g_free);
+  g_clear_pointer (&priv->id, g_free);
+  g_clear_pointer (&priv->file, g_free);
+  g_clear_pointer (&priv->spec, g_free);
+  g_clear_pointer (&priv->thread, g_free);
+
+  G_OBJECT_CLASS (ide_debugger_breakpoint_parent_class)->finalize (object);
+}
+
+static void
+ide_debugger_breakpoint_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+  IdeDebuggerBreakpoint *self = IDE_DEBUGGER_BREAKPOINT (object);
+
+  switch (prop_id)
+    {
+    case PROP_ADDRESS:
+      g_value_set_string (value, ide_debugger_breakpoint_get_address (self));
+      break;
+
+    case PROP_ID:
+      g_value_set_string (value, ide_debugger_breakpoint_get_id (self));
+      break;
+
+    case PROP_COUNT:
+      g_value_set_int64 (value, ide_debugger_breakpoint_get_count (self));
+      break;
+
+    case PROP_DISPOSITION:
+      g_value_set_enum (value, ide_debugger_breakpoint_get_disposition (self));
+      break;
+
+    case PROP_ENABLED:
+      g_value_set_boolean (value, ide_debugger_breakpoint_get_enabled (self));
+      break;
+
+    case PROP_FILE:
+      g_value_set_string (value, ide_debugger_breakpoint_get_file (self));
+      break;
+
+    case PROP_FUNCTION:
+      g_value_set_string (value, ide_debugger_breakpoint_get_function (self));
+      break;
+
+    case PROP_LINE:
+      g_value_set_uint (value, ide_debugger_breakpoint_get_line (self));
+      break;
+
+    case PROP_MODE:
+      g_value_set_enum (value, ide_debugger_breakpoint_get_mode (self));
+      break;
+
+    case PROP_SPEC:
+      g_value_set_string (value, ide_debugger_breakpoint_get_spec (self));
+      break;
+
+    case PROP_THREAD:
+      g_value_set_string (value, ide_debugger_breakpoint_get_thread (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_breakpoint_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  IdeDebuggerBreakpoint *self = IDE_DEBUGGER_BREAKPOINT (object);
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_ADDRESS:
+      ide_debugger_breakpoint_set_address (self, g_value_get_string (value));
+      break;
+
+    case PROP_ID:
+      priv->id = g_value_dup_string (value);
+      break;
+
+    case PROP_COUNT:
+      ide_debugger_breakpoint_set_count (self, g_value_get_int64 (value));
+      break;
+
+    case PROP_DISPOSITION:
+      ide_debugger_breakpoint_set_disposition (self, g_value_get_enum (value));
+      break;
+
+    case PROP_ENABLED:
+      ide_debugger_breakpoint_set_enabled (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_FILE:
+      ide_debugger_breakpoint_set_file (self, g_value_get_string (value));
+      break;
+
+    case PROP_FUNCTION:
+      ide_debugger_breakpoint_set_function (self, g_value_get_string (value));
+      break;
+
+    case PROP_LINE:
+      ide_debugger_breakpoint_set_line (self, g_value_get_uint (value));
+      break;
+
+    case PROP_MODE:
+      ide_debugger_breakpoint_set_mode (self, g_value_get_enum (value));
+      break;
+
+    case PROP_SPEC:
+      ide_debugger_breakpoint_set_spec (self, g_value_get_string (value));
+      break;
+
+    case PROP_THREAD:
+      ide_debugger_breakpoint_set_thread (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_breakpoint_class_init (IdeDebuggerBreakpointClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_debugger_breakpoint_finalize;
+  object_class->get_property = ide_debugger_breakpoint_get_property;
+  object_class->set_property = ide_debugger_breakpoint_set_property;
+
+  /**
+   * IdeDebuggerBreakpoint:address:
+   *
+   * The address of the breakpoint, if available. This is a string so that
+   * architectures other than that of the user can be supported.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_ADDRESS] =
+    g_param_spec_string ("address",
+                         "Address",
+                         "The address of the breakpoint",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeDebuggerBreakpoint:count:
+   *
+   * The number of times the breakpoint has been reached.
+   *
+   * This is backend specific, and may not be supported by all backends.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_COUNT] =
+    g_param_spec_int64 ("count",
+                        "Count",
+                        "The number of times the breakpoint has hit",
+                        0, G_MAXINT64, 0,
+                        (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeDebuggerBreakpoint:disposition:
+   *
+   * This property describes what should happen to the breakpoint upon the
+   * next stop of the debugger.
+   *
+   * Generally, breakpoints are kept. But some backends allow you to remove
+   * a breakpoint upon the next stop of the debugger or when the breakpoint
+   * is next reached.
+   *
+   * This is backend specific, and not all values may be supported by all
+   * backends.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_DISPOSITION] =
+    g_param_spec_enum ("disposition",
+                         "Disposition",
+                         "The disposition of the breakpoint",
+                         IDE_TYPE_DEBUGGER_DISPOSITION,
+                         IDE_DEBUGGER_DISPOSITION_KEEP,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeDebuggerBreakpoint:enabled:
+   *
+   * This property is %TRUE when the breakpoint is enabled.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_ENABLED] =
+    g_param_spec_boolean ("enabled",
+                          "Enabled",
+                          "Enabled",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeDebuggerBreakpoint:function:
+   *
+   * The name of the function containing the breakpoint.
+   *
+   * The value of this is backend specific and may look vastly different
+   * based on the language being debugged.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_FUNCTION] =
+    g_param_spec_string ("function",
+                         "Function",
+                         "Function",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeDebuggerBreakpoint:id:
+   *
+   * The identifier of the breakpoint.
+   *
+   * This is backend specific.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_ID] =
+    g_param_spec_string ("id",
+                         "Identifier",
+                         "The identifier for the breakpoint",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeDebuggerBreakpoint:file:
+   *
+   * The file containing the breakpoint, if any.
+   *
+   * If the breakpoint exists at an assembly instruction that cannot be
+   * represented by a file, this will be %NULL.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_FILE] =
+    g_param_spec_string ("file",
+                         "File",
+                         "The file containing the breakpoint",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeDebuggerBreakpoint:line:
+   *
+   * The line number within #IdeDebuggerBreakpoint:file where the
+   * breakpoint exists.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_LINE] =
+    g_param_spec_uint ("line",
+                       "Line",
+                       "Line",
+                       0, G_MAXUINT, 0,
+                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeDebuggerBreakpoint:mode:
+   *
+   * The mode of the breakpoint, such as a breakpoint, countpoint, or watchpoint.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_MODE] =
+    g_param_spec_enum ("mode",
+                       "Mode",
+                       "The breakpoint mode",
+                       IDE_TYPE_DEBUGGER_BREAK_MODE,
+                       IDE_DEBUGGER_BREAK_BREAKPOINT,
+                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeDebuggerBreakpoint:spec:
+   *
+   * The specification for the breakpoint, which may be used by watchpoints
+   * to determine of the breakpoint should be applied while executing.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_SPEC] =
+    g_param_spec_string ("spec",
+                         "Spec",
+                         "The specification for a data breakpoint",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeDebuggerBreakpoint:thread:
+   *
+   * The thread the breakpoint is currently stopped in, or %NULL.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_THREAD] =
+    g_param_spec_string ("thread",
+                         "Thread",
+                         "Thread",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_debugger_breakpoint_init (IdeDebuggerBreakpoint *self)
+{
+}
+
+IdeDebuggerBreakpoint *
+ide_debugger_breakpoint_new (const gchar *id)
+{
+  return g_object_new (IDE_TYPE_DEBUGGER_BREAKPOINT,
+                       "id", id,
+                       NULL);
+}
+
+/**
+ * ide_debugger_breakpoint_get_id:
+ * @self: An #IdeDebuggerBreakpoint
+ *
+ * Gets the identifier for the breakpoint that is backend specific.
+ *
+ * Returns: the id of the breakpoint
+ *
+ * Since: 3.26
+ */
+const gchar *
+ide_debugger_breakpoint_get_id (IdeDebuggerBreakpoint *self)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self), NULL);
+
+  return priv->id;
+}
+
+/**
+ * ide_debugger_breakpoint_get_address:
+ * @self: An #IdeDebuggerBreakpoint
+ *
+ * Gets the "address" property, which defines where the breakpoint is
+ * located in memory.
+ *
+ * This value is a string to allow for communicating with architectures
+ * different than the host of the debugging UI.
+ *
+ * Returns: The address of the breakpoint, if any.
+ *
+ * Since: 3.26
+ */
+const gchar *
+ide_debugger_breakpoint_get_address (IdeDebuggerBreakpoint *self)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self), NULL);
+
+  return priv->address;
+}
+
+/**
+ * ide_debugger_breakpoint_set_address:
+ * @self: An #IdeDebuggerBreakpoint
+ * @address: (nullable): The address of the breakpoint
+ *
+ * Sets the address of the breakpoint, if any.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_breakpoint_set_address (IdeDebuggerBreakpoint *self,
+                                     const gchar           *address)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self));
+
+  if (g_strcmp0 (address, priv->address) != 0)
+    {
+      g_free (priv->address);
+      priv->address = g_strdup (address);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ADDRESS]);
+    }
+}
+
+/**
+ * ide_debugger_breakpoint_get_file:
+ * @self: An #IdeDebuggerBreakpoint
+ *
+ * Gets the file that contains the breakpoint. This may be %NULL, particularly
+ * if the breakpoint does not exist with in a known file, such as at a memory
+ * address.
+ *
+ * Returns: (nullable): The file containing the breakpoint, or %NULL
+ *
+ * Since: 3.26
+ */
+const gchar *
+ide_debugger_breakpoint_get_file (IdeDebuggerBreakpoint *self)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self), NULL);
+
+  return priv->file;
+}
+
+/**
+ * ide_debugger_breakpoint_set_file:
+ * @self: An #IdeDebuggerBreakpoint
+ * @file: (nullable): the file containing the breakpoint, or %NULL
+ *
+ * Sets the file that contains the breakpoint, if any.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_breakpoint_set_file (IdeDebuggerBreakpoint *self,
+                                  const gchar           *file)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self));
+
+  if (g_strcmp0 (file, priv->file) != 0)
+    {
+      g_free (priv->file);
+      priv->file = g_strdup (file);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE]);
+    }
+}
+
+/**
+ * ide_debugger_breakpoint_get_spec:
+ * @self: An #IdeDebuggerBreakpoint
+ *
+ * Gets the "spec" property of the breakpoint.
+ *
+ * The spec is used when the #IdeDebuggerBreakMode is
+ * %IDE_DEBUGGER_BREAK_WATCHPOINT.
+ *
+ * Returns: (nullable): A string containing the spec, or %NULL
+ */
+const gchar *
+ide_debugger_breakpoint_get_spec (IdeDebuggerBreakpoint *self)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self), NULL);
+
+  return priv->spec;
+}
+
+/**
+ * ide_debugger_breakpoint_set_spec:
+ * @self: An #IdeDebuggerBreakpoint
+ * @spec: (nullable): the specification or %NULL
+ *
+ * Sets the specification for the debugger breakpoint. This describes
+ * a statement which the debugger can use to determine of the breakpoint
+ * should be applied when stopping the debugger.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_breakpoint_set_spec (IdeDebuggerBreakpoint *self,
+                                  const gchar           *spec)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self));
+
+  if (g_strcmp0 (spec, priv->spec) != 0)
+    {
+      g_free (priv->spec);
+      priv->spec = g_strdup (spec);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SPEC]);
+    }
+}
+
+/**
+ * ide_debugger_breakpoint_get_count:
+ *
+ * Gets the number of times the breakpoint has been reached, if supported
+ * by the debugger backend.
+ *
+ * Returns: An integer greater than or equal to zero representing the
+ *   number of times the breakpoint has been reached.
+ *
+ * Since: 3.26
+ */
+gint64
+ide_debugger_breakpoint_get_count (IdeDebuggerBreakpoint *self)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self), 0);
+
+  return priv->count;
+}
+
+/**
+ * ide_debugger_breakpoint_set_count:
+ *
+ * Sets the number of times the breakpoint has been reached if the
+ * breakpoint is a countpoint (or if the backend supports counting of
+ * regular breakpoints).
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_breakpoint_set_count (IdeDebuggerBreakpoint *self,
+                                   gint64                 count)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self));
+
+  if (priv->count != count)
+    {
+      priv->count = count;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_COUNT]);
+    }
+}
+
+/**
+ * ide_debugger_breakpoint_get_mode:
+ *
+ * Gets teh mode for the breakpoint. This describes if the breakpoint
+ * is a normal breakpoint type, countpoint, or watchpoint.
+ *
+ * See also: #IdeDebuggerBreakMode
+ *
+ * Returns: The mode of the breakpoint
+ *
+ * Since: 3.26
+ */
+IdeDebuggerBreakMode
+ide_debugger_breakpoint_get_mode (IdeDebuggerBreakpoint *self)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self), 0);
+
+  return priv->mode;
+}
+
+/**
+ * ide_debugger_breakpoint_set_mode:
+ * @self: An #IdeDebuggerBreakpoint
+ * @mode: An #IdeDebuggerBreakMode
+ *
+ * Sets the "mode" property for the breakpoint.
+ *
+ * This should represent the mode for which the breakpoint is used.
+ *
+ * For example, if it is a countpoint (a breakpoint which increments a
+ * counter), you would use %IDE_DEBUGGER_BREAK_COUNTPOINT.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_breakpoint_set_mode (IdeDebuggerBreakpoint *self,
+                                  IdeDebuggerBreakMode   mode)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAK_MODE (mode));
+
+  if (priv->mode != mode)
+    {
+      priv->mode = mode;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MODE]);
+    }
+}
+
+/**
+ * ide_debugger_breakpoint_get_disposition:
+ * @self: An #IdeDebuggerBreakpoint
+ *
+ * Gets the "disposition" property of the breakpoint.
+ *
+ * Returns: An #IdeDebugerDisposition
+ *
+ * Since: 3.26
+ */
+IdeDebuggerDisposition
+ide_debugger_breakpoint_get_disposition (IdeDebuggerBreakpoint *self)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self), 0);
+
+  return priv->disposition;
+}
+
+/**
+ * ide_debugger_breakpoint_set_disposition:
+ * @self: an #IdeDebuggerBreakpoint
+ * @disposition: an #IdeDebuggerDisposition
+ *
+ * Sets the "disposition" property.
+ *
+ * The disposition property is used to to track what should happen to a
+ * breakpoint when movements are made in the debugger.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_breakpoint_set_disposition (IdeDebuggerBreakpoint  *self,
+                                         IdeDebuggerDisposition  disposition)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_DISPOSITION (disposition));
+
+  if (disposition != priv->disposition)
+    {
+      priv->disposition = disposition;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISPOSITION]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ENABLED]);
+    }
+}
+
+/**
+ * ide_debugger_breakpoint_get_enabled:
+ *
+ * Checks if the breakpoint is enabled.
+ *
+ * Returns: %TRUE if the breakpoint is enabled
+ */
+gboolean
+ide_debugger_breakpoint_get_enabled (IdeDebuggerBreakpoint *self)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self), FALSE);
+
+  return priv->enabled;
+}
+
+/**
+ * ide_debugger_breakpoint_set_enabled:
+ * @self: a #IdeDebuggerBreakpoint
+ * @enabled: if the breakpoint is enabled
+ *
+ * Sets the enabled state of the breakpoint instance.
+ *
+ * You must call ide_debugger_breakpoint_modify_breakpoint_async() to actually
+ * modify the breakpoint in the backend.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_breakpoint_set_enabled (IdeDebuggerBreakpoint *self,
+                                     gboolean               enabled)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self));
+
+  enabled = !!enabled;
+
+  if (priv->enabled != enabled)
+    {
+      priv->enabled = enabled;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ENABLED]);
+    }
+}
+
+/**
+ * ide_debugger_breakpoint_get_function:
+ * @self: An #IdeDebuggerBreakpoint
+ *
+ * Gets the "function" property of the breakpoint.
+ *
+ * This is a user-readable value representing the name of the function.
+ *
+ * Since: 3.26
+ */
+const gchar *
+ide_debugger_breakpoint_get_function (IdeDebuggerBreakpoint *self)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self), NULL);
+
+  return priv->function;
+}
+
+/**
+ * ide_debugger_breakpoint_set_function:
+ * @self: An #IdeDebuggerBreakpoint
+ * @function: (nullable): the name of the function, or %NULL
+ *
+ * Sets the "function" property, which is a user-readable value representing
+ * the name of the function.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_breakpoint_set_function (IdeDebuggerBreakpoint *self,
+                                      const gchar           *function)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self));
+
+  if (g_strcmp0 (function, priv->function) != 0)
+    {
+      g_free (priv->function);
+      priv->function = g_strdup (function);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FUNCTION]);
+    }
+}
+
+/**
+ * ide_debugger_breakpoint_get_line:
+ * @self: An #IdeDebuggerBreakpoint
+ *
+ * Gets the "line" property, which is the line number within the file
+ * that contains the breakpoint.
+ *
+ * This value is indexed from 1, and 0 indicates that the value is unset.
+ *
+ * Returns: An integer greater than 0 if set, otherwise 0.
+ *
+ * Since: 3.26
+ */
+guint
+ide_debugger_breakpoint_get_line (IdeDebuggerBreakpoint *self)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self), 0);
+
+  return priv->line;
+}
+
+/**
+ * ide_debugger_breakpoint_set_line:
+ * @self: An #IdeDebuggerBreakpoint
+ *
+ * Sets the line for the breakpoint. A value of 0 means the line is unset.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_breakpoint_set_line (IdeDebuggerBreakpoint *self,
+                                  guint                  line)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self));
+
+  if (priv->line != line)
+    {
+      priv->line = line;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LINE]);
+    }
+}
+
+/**
+ * ide_debugger_breakpoint_get_thread:
+ * @self: An #IdeDebuggerBreakpoint
+ *
+ * Gets the "thread" property, which is the thread the breakpoint is
+ * currently stopped in (if any).
+ *
+ * Returns: (nullable): the thread identifier or %NULL
+ *
+ * Since: 3.26
+ */
+const gchar *
+ide_debugger_breakpoint_get_thread (IdeDebuggerBreakpoint *self)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self), NULL);
+
+  return priv->thread;
+}
+
+/**
+ * ide_debugger_breakpoint_set_thread:
+ *
+ * Sets the thread that the breakpoint is currently stopped in.
+ *
+ * This should generally only be used by debugger implementations.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_breakpoint_set_thread (IdeDebuggerBreakpoint *self,
+                                    const gchar           *thread)
+{
+  IdeDebuggerBreakpointPrivate *priv = ide_debugger_breakpoint_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (self));
+
+  if (g_strcmp0 (priv->thread, thread) != 0)
+    {
+      g_free (priv->thread);
+      priv->thread = g_strdup (thread);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_THREAD]);
+    }
+}
+
+gint
+ide_debugger_breakpoint_compare (IdeDebuggerBreakpoint *a,
+                                 IdeDebuggerBreakpoint *b)
+{
+  IdeDebuggerBreakpointPrivate *priv_a = ide_debugger_breakpoint_get_instance_private (a);
+  IdeDebuggerBreakpointPrivate *priv_b = ide_debugger_breakpoint_get_instance_private (b);
+
+  if (priv_a->id && priv_b->id)
+    {
+      if (g_ascii_isdigit (*priv_a->id) && g_ascii_isdigit (*priv_b->id))
+        return g_ascii_strtoll (priv_a->id, NULL, 10) -
+               g_ascii_strtoll (priv_b->id, NULL, 10);
+    }
+
+  return g_strcmp0 (priv_a->id, priv_b->id);
+}
diff --git a/libide/debugger/ide-debugger-breakpoint.h b/libide/debugger/ide-debugger-breakpoint.h
new file mode 100644
index 0000000..2c0d3ae
--- /dev/null
+++ b/libide/debugger/ide-debugger-breakpoint.h
@@ -0,0 +1,81 @@
+/* ide-debugger-breakpoint.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/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include "ide-debugger-frame.h"
+#include "ide-debugger-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_BREAKPOINT (ide_debugger_breakpoint_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeDebuggerBreakpoint, ide_debugger_breakpoint, IDE, DEBUGGER_BREAKPOINT, GObject)
+
+struct _IdeDebuggerBreakpointClass
+{
+  GObjectClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+gint                    ide_debugger_breakpoint_compare          (IdeDebuggerBreakpoint  *a,
+                                                                  IdeDebuggerBreakpoint  *b);
+IdeDebuggerBreakpoint  *ide_debugger_breakpoint_new              (const gchar            *id);
+const gchar            *ide_debugger_breakpoint_get_id           (IdeDebuggerBreakpoint  *self);
+gboolean                ide_debugger_breakpoint_get_enabled      (IdeDebuggerBreakpoint  *self);
+void                    ide_debugger_breakpoint_set_enabled      (IdeDebuggerBreakpoint  *self,
+                                                                  gboolean                enabled);
+IdeDebuggerBreakMode    ide_debugger_breakpoint_get_mode         (IdeDebuggerBreakpoint  *self);
+void                    ide_debugger_breakpoint_set_mode         (IdeDebuggerBreakpoint  *self,
+                                                                  IdeDebuggerBreakMode    mode);
+IdeDebuggerDisposition  ide_debugger_breakpoint_get_disposition  (IdeDebuggerBreakpoint  *self);
+void                    ide_debugger_breakpoint_set_disposition  (IdeDebuggerBreakpoint  *self,
+                                                                  IdeDebuggerDisposition  disposition);
+const gchar            *ide_debugger_breakpoint_get_address      (IdeDebuggerBreakpoint  *self);
+void                    ide_debugger_breakpoint_set_address      (IdeDebuggerBreakpoint  *self,
+                                                                  const gchar            *address);
+const gchar            *ide_debugger_breakpoint_get_spec         (IdeDebuggerBreakpoint  *self);
+void                    ide_debugger_breakpoint_set_spec         (IdeDebuggerBreakpoint  *self,
+                                                                  const gchar            *spec);
+const gchar            *ide_debugger_breakpoint_get_function     (IdeDebuggerBreakpoint  *self);
+void                    ide_debugger_breakpoint_set_function     (IdeDebuggerBreakpoint *self,
+                                                                  const gchar           *function);
+const gchar            *ide_debugger_breakpoint_get_file         (IdeDebuggerBreakpoint  *self);
+void                    ide_debugger_breakpoint_set_file         (IdeDebuggerBreakpoint  *self,
+                                                                  const gchar            *file);
+guint                   ide_debugger_breakpoint_get_line         (IdeDebuggerBreakpoint  *self);
+void                    ide_debugger_breakpoint_set_line         (IdeDebuggerBreakpoint  *self,
+                                                                  guint                   line);
+gint64                  ide_debugger_breakpoint_get_count        (IdeDebuggerBreakpoint  *self);
+void                    ide_debugger_breakpoint_set_count        (IdeDebuggerBreakpoint  *self,
+                                                                  gint64                  count);
+const gchar            *ide_debugger_breakpoint_get_thread       (IdeDebuggerBreakpoint  *self);
+void                    ide_debugger_breakpoint_set_thread       (IdeDebuggerBreakpoint  *self,
+                                                                  const gchar            *thread);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-breakpoints-view.c b/libide/debugger/ide-debugger-breakpoints-view.c
new file mode 100644
index 0000000..965cc9d
--- /dev/null
+++ b/libide/debugger/ide-debugger-breakpoints-view.c
@@ -0,0 +1,553 @@
+/* ide-debugger-breakpoints-view.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 "ide-debugger-breakpoints-view"
+
+#include <dazzle.h>
+
+#include "ide-debugger-breakpoints-view.h"
+
+struct _IdeDebuggerBreakpointsView
+{
+  GtkBin          parent_instance;
+
+  /* Owned references */
+  DzlSignalGroup *debugger_signals;
+
+  /* Template references */
+  GtkCellRendererText   *address_cell;
+  GtkCellRendererText   *file_cell;
+  GtkCellRendererText   *function_cell;
+  GtkCellRendererText   *hits_cell;
+  GtkCellRendererText   *id_cell;
+  GtkCellRendererText   *line_cell;
+  GtkCellRendererText   *spec_cell;
+  GtkCellRendererText   *type_cell;
+  GtkCellRendererToggle *enabled_cell;
+  GtkListStore          *list_store;
+  GtkTreeView           *tree_view;
+  GtkTreeViewColumn     *address_column;
+  GtkTreeViewColumn     *enabled_column;
+  GtkTreeViewColumn     *file_column;
+  GtkTreeViewColumn     *function_column;
+  GtkTreeViewColumn     *hits_column;
+  GtkTreeViewColumn     *id_column;
+  GtkTreeViewColumn     *line_column;
+  GtkTreeViewColumn     *spec_column;
+  GtkTreeViewColumn     *type_column;
+};
+
+enum {
+  PROP_0,
+  PROP_DEBUGGER,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (IdeDebuggerBreakpointsView, ide_debugger_breakpoints_view, GTK_TYPE_BIN)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_debugger_breakpoints_view_running (IdeDebuggerBreakpointsView *self,
+                                       IdeDebugger                *debugger)
+{
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINTS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->tree_view), FALSE);
+}
+
+static void
+ide_debugger_breakpoints_view_stopped (IdeDebuggerBreakpointsView *self,
+                                       IdeDebuggerStopReason       stop_reason,
+                                       IdeDebuggerBreakpoint      *breakpoint,
+                                       IdeDebugger                *debugger)
+{
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINTS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_STOP_REASON (stop_reason));
+  g_assert (!breakpoint || IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->tree_view), TRUE);
+}
+
+static void
+ide_debugger_breakpoints_view_breakpoint_added (IdeDebuggerBreakpointsView *self,
+                                                IdeDebuggerBreakpoint      *breakpoint,
+                                                IdeDebugger                *debugger)
+{
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINTS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  dzl_gtk_list_store_insert_sorted (self->list_store, &iter, breakpoint, 0,
+                                    (GCompareDataFunc)ide_debugger_breakpoint_compare,
+                                    NULL);
+
+  gtk_list_store_set (self->list_store, &iter, 0, breakpoint, -1);
+}
+
+static void
+ide_debugger_breakpoints_view_breakpoint_removed (IdeDebuggerBreakpointsView *self,
+                                                  IdeDebuggerBreakpoint      *breakpoint,
+                                                  IdeDebugger                *debugger)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINTS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  model = GTK_TREE_MODEL (self->list_store);
+
+  if (gtk_tree_model_get_iter_first (model, &iter))
+    {
+      do
+        {
+          g_autoptr(IdeDebuggerBreakpoint) row = NULL;
+
+          gtk_tree_model_get (model, &iter, 0, &row, -1);
+
+          if (ide_debugger_breakpoint_compare (row, breakpoint) == 0)
+            {
+              gtk_list_store_remove (self->list_store, &iter);
+              break;
+            }
+        }
+      while (gtk_tree_model_iter_next (model, &iter));
+    }
+}
+
+static void
+ide_debugger_breakpoints_view_breakpoint_modified (IdeDebuggerBreakpointsView *self,
+                                                   IdeDebuggerBreakpoint      *breakpoint,
+                                                   IdeDebugger                *debugger)
+{
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINTS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  /* We can optimize this into a single replace, but should be fine for now */
+  ide_debugger_breakpoints_view_breakpoint_removed (self, breakpoint, debugger);
+  ide_debugger_breakpoints_view_breakpoint_added (self, breakpoint, debugger);
+}
+
+static void
+ide_debugger_breakpoints_view_enabled_toggled (IdeDebuggerBreakpointsView *self,
+                                               const gchar                *path_str,
+                                               GtkCellRendererToggle      *cell)
+{
+  IdeDebugger *debugger;
+  GtkTreeModel *model;
+  GtkTreePath *path;
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINTS_VIEW (self));
+  g_assert (path_str != NULL);
+  g_assert (GTK_IS_CELL_RENDERER_TOGGLE (cell));
+
+  debugger = ide_debugger_breakpoints_view_get_debugger (self);
+  if (debugger == NULL)
+    return;
+
+  model = GTK_TREE_MODEL (self->list_store);
+  path = gtk_tree_path_new_from_string (path_str);
+
+  if (gtk_tree_model_get_iter (model, &iter, path))
+    {
+      g_autoptr(IdeDebuggerBreakpoint) breakpoint = NULL;
+
+      gtk_tree_model_get (model, &iter, 0, &breakpoint, -1);
+
+      ide_debugger_breakpoint_set_enabled (breakpoint,
+                                           !ide_debugger_breakpoint_get_enabled (breakpoint));
+      ide_debugger_modify_breakpoint_async (debugger,
+                                            IDE_DEBUGGER_BREAKPOINT_CHANGE_ENABLED,
+                                            breakpoint,
+                                            NULL, NULL, NULL);
+    }
+
+  gtk_tree_path_free (path);
+}
+
+static void
+string_property_cell_data_func (GtkCellLayout   *cell_layout,
+                                GtkCellRenderer *cell,
+                                GtkTreeModel    *model,
+                                GtkTreeIter     *iter,
+                                gpointer         user_data)
+{
+  const gchar *property = user_data;
+  g_autoptr(GObject) object = NULL;
+  g_auto(GValue) value = G_VALUE_INIT;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+  g_assert (property != NULL);
+
+  g_value_init (&value, G_TYPE_STRING);
+  gtk_tree_model_get (model, iter, 0, &object, -1);
+
+  if (object != NULL)
+    g_object_get_property (object, property, &value);
+
+  g_object_set_property (G_OBJECT (cell), "text", &value);
+}
+
+static void
+int_property_cell_data_func (GtkCellLayout   *cell_layout,
+                             GtkCellRenderer *cell,
+                             GtkTreeModel    *model,
+                             GtkTreeIter     *iter,
+                             gpointer         user_data)
+{
+  const gchar *property = user_data;
+  g_autoptr(GObject) object = NULL;
+  g_auto(GValue) value = G_VALUE_INIT;
+  g_autofree gchar *str = NULL;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+  g_assert (property != NULL);
+
+  g_value_init (&value, G_TYPE_INT64);
+  gtk_tree_model_get (model, iter, 0, &object, -1);
+
+  if (object != NULL)
+    g_object_get_property (object, property, &value);
+
+  str = g_strdup_printf ("%"G_GINT64_FORMAT, g_value_get_int64 (&value));
+  g_object_set (cell, "text", str, NULL);
+}
+
+static void
+enum_property_cell_data_func (GtkCellLayout   *cell_layout,
+                              GtkCellRenderer *cell,
+                              GtkTreeModel    *model,
+                              GtkTreeIter     *iter,
+                              gpointer         user_data)
+{
+  GParamSpec *pspec = user_data;
+  g_autoptr(GObject) object = NULL;
+  g_auto(GValue) value = G_VALUE_INIT;
+  const gchar *str = NULL;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+  g_assert (pspec != NULL);
+
+  g_value_init (&value, pspec->value_type);
+  gtk_tree_model_get (model, iter, 0, &object, -1);
+
+  if (object != NULL)
+    {
+      GEnumValue *ev = g_enum_get_value (g_type_class_peek (pspec->value_type),
+                                         g_value_get_enum (&value));
+
+      if (ev != NULL)
+        str = ev->value_nick;
+    }
+
+  g_object_set (cell, "text", str, NULL);
+}
+
+static void
+bool_property_cell_data_func (GtkCellLayout   *cell_layout,
+                              GtkCellRenderer *cell,
+                              GtkTreeModel    *model,
+                              GtkTreeIter     *iter,
+                              gpointer         user_data)
+{
+  g_autoptr(GObject) object = NULL;
+  g_auto(GValue) value = G_VALUE_INIT;
+  const gchar *property = user_data;
+
+  g_value_init (&value, G_TYPE_BOOLEAN);
+  gtk_tree_model_get (model, iter, 0, &object, -1);
+  if (object != NULL)
+    g_object_get_property (object, property, &value);
+  g_object_set_property (G_OBJECT (cell), "active", &value);
+}
+
+static void
+ide_debugger_breakpoints_view_delete_breakpoint (GtkTreeView                *tree_view,
+                                                 IdeDebuggerBreakpointsView *self)
+{
+  GtkTreeSelection *selection;
+  GtkTreeModel *model = NULL;
+  IdeDebugger *debugger;
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINTS_VIEW (self));
+  g_assert (GTK_IS_TREE_VIEW (tree_view));
+
+  debugger = ide_debugger_breakpoints_view_get_debugger (self);
+
+  if (debugger == NULL)
+    return;
+
+  selection = gtk_tree_view_get_selection (tree_view);
+
+  if (gtk_tree_selection_get_selected (selection, &model, &iter))
+    {
+      g_autoptr(IdeDebuggerBreakpoint) breakpoint = NULL;
+
+      gtk_tree_model_get (model, &iter, 0, &breakpoint, -1);
+
+      if (breakpoint != NULL)
+        ide_debugger_remove_breakpoint_async (debugger, breakpoint, NULL, NULL, NULL);
+    }
+}
+
+static void
+ide_debugger_breakpoints_view_destroy (GtkWidget *widget)
+{
+  IdeDebuggerBreakpointsView *self = (IdeDebuggerBreakpointsView *)widget;
+
+  g_clear_object (&self->debugger_signals);
+
+  GTK_WIDGET_CLASS (ide_debugger_breakpoints_view_parent_class)->destroy (widget);
+}
+
+static void
+ide_debugger_breakpoints_view_get_property (GObject    *object,
+                                            guint       prop_id,
+                                            GValue     *value,
+                                            GParamSpec *pspec)
+{
+  IdeDebuggerBreakpointsView *self = IDE_DEBUGGER_BREAKPOINTS_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEBUGGER:
+      g_value_set_object (value, ide_debugger_breakpoints_view_get_debugger (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_breakpoints_view_set_property (GObject      *object,
+                                            guint         prop_id,
+                                            const GValue *value,
+                                            GParamSpec   *pspec)
+{
+  IdeDebuggerBreakpointsView *self = IDE_DEBUGGER_BREAKPOINTS_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEBUGGER:
+      ide_debugger_breakpoints_view_set_debugger (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_breakpoints_view_class_init (IdeDebuggerBreakpointsViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = ide_debugger_breakpoints_view_get_property;
+  object_class->set_property = ide_debugger_breakpoints_view_set_property;
+
+  widget_class->destroy = ide_debugger_breakpoints_view_destroy;
+
+  properties [PROP_DEBUGGER] =
+    g_param_spec_object ("debugger",
+                         "Debugger",
+                         "The debugger being observed",
+                         IDE_TYPE_DEBUGGER,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-debugger-breakpoints-view.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, address_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, address_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, hits_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, hits_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, file_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, file_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, function_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, function_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, id_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, id_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, line_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, line_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, list_store);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, tree_view);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, spec_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, spec_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, type_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, type_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, enabled_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerBreakpointsView, enabled_column);
+
+  g_type_ensure (IDE_TYPE_DEBUGGER_BREAKPOINT);
+}
+
+static void
+ide_debugger_breakpoints_view_init (IdeDebuggerBreakpointsView *self)
+{
+  DzlShortcutController *controller;
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->debugger_signals = dzl_signal_group_new (IDE_TYPE_DEBUGGER);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "running",
+                                    G_CALLBACK (ide_debugger_breakpoints_view_running),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "stopped",
+                                    G_CALLBACK (ide_debugger_breakpoints_view_stopped),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "breakpoint-added",
+                                    G_CALLBACK (ide_debugger_breakpoints_view_breakpoint_added),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "breakpoint-removed",
+                                    G_CALLBACK (ide_debugger_breakpoints_view_breakpoint_removed),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "breakpoint-modified",
+                                    G_CALLBACK (ide_debugger_breakpoints_view_breakpoint_modified),
+                                    self);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->id_column),
+                                      GTK_CELL_RENDERER (self->id_cell),
+                                      string_property_cell_data_func, "id", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->file_column),
+                                      GTK_CELL_RENDERER (self->file_cell),
+                                      string_property_cell_data_func, "file", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->line_column),
+                                      GTK_CELL_RENDERER (self->line_cell),
+                                      int_property_cell_data_func, "line", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->function_column),
+                                      GTK_CELL_RENDERER (self->function_cell),
+                                      string_property_cell_data_func, "function", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->address_column),
+                                      GTK_CELL_RENDERER (self->address_cell),
+                                      string_property_cell_data_func, "address", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->hits_column),
+                                      GTK_CELL_RENDERER (self->hits_cell),
+                                      int_property_cell_data_func, "count", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->type_column),
+                                      GTK_CELL_RENDERER (self->type_cell),
+                                      enum_property_cell_data_func,
+                                      g_object_class_find_property (g_type_class_peek 
(IDE_TYPE_DEBUGGER_BREAKPOINT), "mode"),
+                                      NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->spec_column),
+                                      GTK_CELL_RENDERER (self->spec_cell),
+                                      string_property_cell_data_func, "spec", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->enabled_column),
+                                      GTK_CELL_RENDERER (self->enabled_cell),
+                                      bool_property_cell_data_func, "enabled", NULL);
+
+  g_signal_connect_swapped (self->enabled_cell,
+                            "toggled",
+                            G_CALLBACK (ide_debugger_breakpoints_view_enabled_toggled),
+                            self);
+
+  controller = dzl_shortcut_controller_find (GTK_WIDGET (self->tree_view));
+
+  dzl_shortcut_controller_add_command_callback (controller,
+                                                "org.gnome.builder.debugger.delete-breakpoint",
+                                                "Delete",
+                                                DZL_SHORTCUT_PHASE_BUBBLE,
+                                                (GtkCallback) 
ide_debugger_breakpoints_view_delete_breakpoint,
+                                                self, NULL);
+}
+
+GtkWidget *
+ide_debugger_breakpoints_view_new (void)
+{
+  return g_object_new (IDE_TYPE_DEBUGGER_BREAKPOINTS_VIEW, NULL);
+}
+
+/**
+ * ide_debugger_breakpoints_view_get_debugger:
+ * @self: a #IdeDebuggerBreakpointsView
+ *
+ * Gets the debugger that is being observed by the view.
+ *
+ * Returns: (nullable) (transfer none): An #IdeDebugger or %NULL
+ *
+ * Since: 3.26
+ */
+IdeDebugger *
+ide_debugger_breakpoints_view_get_debugger (IdeDebuggerBreakpointsView *self)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER_BREAKPOINTS_VIEW (self), NULL);
+
+  if (self->debugger_signals != NULL)
+    return dzl_signal_group_get_target (self->debugger_signals);
+  else
+    return NULL;
+}
+
+/**
+ * ide_debugger_breakpoints_view_set_debugger:
+ * @self: a #IdeDebuggerBreakpointsView
+ * @debugger: (nullable): An #IdeDebugger or %NULL
+ *
+ * Sets the debugger that is being viewed.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_breakpoints_view_set_debugger (IdeDebuggerBreakpointsView *self,
+                                            IdeDebugger                *debugger)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINTS_VIEW (self));
+
+  if (self->debugger_signals != NULL)
+    {
+      dzl_signal_group_set_target (self->debugger_signals, debugger);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEBUGGER]);
+    }
+}
diff --git a/libide/debugger/ide-debugger-breakpoints-view.h b/libide/debugger/ide-debugger-breakpoints-view.h
new file mode 100644
index 0000000..e01325a
--- /dev/null
+++ b/libide/debugger/ide-debugger-breakpoints-view.h
@@ -0,0 +1,36 @@
+/* ide-debugger-breakpoints-view.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/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "ide-debugger.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_BREAKPOINTS_VIEW (ide_debugger_breakpoints_view_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDebuggerBreakpointsView, ide_debugger_breakpoints_view, IDE, 
DEBUGGER_BREAKPOINTS_VIEW, GtkBin)
+
+GtkWidget   *ide_debugger_breakpoints_view_new          (void);
+IdeDebugger *ide_debugger_breakpoints_view_get_debugger (IdeDebuggerBreakpointsView *self);
+void         ide_debugger_breakpoints_view_set_debugger (IdeDebuggerBreakpointsView *self,
+                                                         IdeDebugger                *debugger);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-fallbacks.c b/libide/debugger/ide-debugger-fallbacks.c
new file mode 100644
index 0000000..df8e246
--- /dev/null
+++ b/libide/debugger/ide-debugger-fallbacks.c
@@ -0,0 +1,256 @@
+/* ide-debugger-fallbacks.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 "ide-debugger-fallbacks"
+
+#include "ide-debugger.h"
+#include "ide-debugger-private.h"
+
+void
+_ide_debugger_real_list_frames_async (IdeDebugger         *self,
+                                      IdeDebuggerThread   *thread,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (!thread || IDE_IS_DEBUGGER_THREAD (thread));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  g_task_report_new_error (self, callback, user_data,
+                           _ide_debugger_real_list_frames_async,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "Listing stack frames is not supported");
+}
+
+GPtrArray *
+_ide_debugger_real_list_frames_finish (IdeDebugger   *self,
+                                       GAsyncResult  *result,
+                                       GError       **error)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+void
+_ide_debugger_real_interrupt_async (IdeDebugger            *self,
+                                    IdeDebuggerThreadGroup *thread_group,
+                                    GCancellable           *cancellable,
+                                    GAsyncReadyCallback     callback,
+                                    gpointer                user_data)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (!thread_group || IDE_IS_DEBUGGER_THREAD_GROUP (thread_group));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  g_task_report_new_error (self, callback, user_data,
+                           _ide_debugger_real_interrupt_async,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "Interrupting inferrior is not supported");
+}
+
+gboolean
+_ide_debugger_real_interrupt_finish (IdeDebugger   *self,
+                                     GAsyncResult  *result,
+                                     GError       **error)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+void
+_ide_debugger_real_send_signal_async (IdeDebugger         *self,
+                                      gint                 signum,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  g_task_report_new_error (self, callback, user_data,
+                           _ide_debugger_real_send_signal_async,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "Sending signals to inferrior is not supported");
+}
+
+gboolean
+_ide_debugger_real_send_signal_finish (IdeDebugger   *self,
+                                       GAsyncResult  *result,
+                                       GError       **error)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+void
+_ide_debugger_real_modify_breakpoint_async (IdeDebugger                 *self,
+                                            IdeDebuggerBreakpointChange  change,
+                                            IdeDebuggerBreakpoint       *breakpoint,
+                                            GCancellable                *cancellable,
+                                            GAsyncReadyCallback          callback,
+                                            gpointer                     user_data)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINT_CHANGE (change));
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  g_task_report_new_error (self, callback, user_data,
+                           _ide_debugger_real_modify_breakpoint_async,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "Modifying breakpoints is not supported");
+}
+
+gboolean
+_ide_debugger_real_modify_breakpoint_finish (IdeDebugger   *self,
+                                             GAsyncResult  *result,
+                                             GError       **error)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+void
+_ide_debugger_real_list_locals_async (IdeDebugger         *self,
+                                      IdeDebuggerThread   *thread, IdeDebuggerFrame    *frame,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_THREAD (thread));
+  g_assert (IDE_IS_DEBUGGER_FRAME (frame));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  g_task_report_new_error (self, callback, user_data,
+                           _ide_debugger_real_list_locals_async,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "Listing locals is not supported");
+}
+
+GPtrArray *
+_ide_debugger_real_list_locals_finish (IdeDebugger   *self,
+                                       GAsyncResult  *result,
+                                       GError       **error)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+void
+_ide_debugger_real_list_params_async (IdeDebugger         *self,
+                                      IdeDebuggerThread   *thread,
+                                      IdeDebuggerFrame    *frame,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_THREAD (thread));
+  g_assert (IDE_IS_DEBUGGER_FRAME (frame));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  g_task_report_new_error (self, callback, user_data,
+                           _ide_debugger_real_list_locals_async,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "Listing locals is not supported");
+}
+
+GPtrArray *
+_ide_debugger_real_list_params_finish (IdeDebugger   *self,
+                                       GAsyncResult  *result,
+                                       GError       **error)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+void
+_ide_debugger_real_list_registers_async (IdeDebugger         *self,
+                                         GCancellable        *cancellable,
+                                         GAsyncReadyCallback  callback,
+                                         gpointer             user_data)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  g_task_report_new_error (self, callback, user_data,
+                           _ide_debugger_real_list_registers_async,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "Listing registers is not supported");
+}
+
+GPtrArray *
+_ide_debugger_real_list_registers_finish (IdeDebugger   *self,
+                                          GAsyncResult  *result,
+                                          GError       **error)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+void
+_ide_debugger_real_disassemble_async (IdeDebugger                   *self,
+                                      const IdeDebuggerAddressRange *range,
+                                      GCancellable                  *cancellable,
+                                      GAsyncReadyCallback            callback,
+                                      gpointer                       user_data)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (range != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  g_task_report_new_error (self, callback, user_data,
+                           _ide_debugger_real_disassemble_async,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "Disassembly is not supported");
+}
+
+GPtrArray *
+_ide_debugger_real_disassemble_finish (IdeDebugger   *self,
+                                       GAsyncResult  *result,
+                                       GError       **error)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
diff --git a/libide/debugger/ide-debugger-frame.c b/libide/debugger/ide-debugger-frame.c
new file mode 100644
index 0000000..63d2e4e
--- /dev/null
+++ b/libide/debugger/ide-debugger-frame.c
@@ -0,0 +1,399 @@
+/* ide-debugger-frame.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-debugger-frame.h"
+
+typedef struct
+{
+  gchar              **args;
+  gchar               *file;
+  gchar               *function;
+  gchar               *library;
+  IdeDebuggerAddress   address;
+  guint                depth;
+  guint                line;
+} IdeDebuggerFramePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeDebuggerFrame, ide_debugger_frame, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_ADDRESS,
+  PROP_ARGS,
+  PROP_DEPTH,
+  PROP_FILE,
+  PROP_FUNCTION,
+  PROP_LIBRARY,
+  PROP_LINE,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_debugger_frame_finalize (GObject *object)
+{
+  IdeDebuggerFrame *self = (IdeDebuggerFrame *)object;
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_clear_pointer (&priv->args, g_strfreev);
+  g_clear_pointer (&priv->file, g_free);
+  g_clear_pointer (&priv->function, g_free);
+  g_clear_pointer (&priv->library, g_free);
+
+  G_OBJECT_CLASS (ide_debugger_frame_parent_class)->finalize (object);
+}
+
+static void
+ide_debugger_frame_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  IdeDebuggerFrame *self = IDE_DEBUGGER_FRAME (object);
+
+  switch (prop_id)
+    {
+    case PROP_ADDRESS:
+      g_value_set_uint64 (value, ide_debugger_frame_get_address (self));
+      break;
+
+    case PROP_ARGS:
+      g_value_set_boxed (value, ide_debugger_frame_get_args (self));
+      break;
+
+    case PROP_DEPTH:
+      g_value_set_uint (value, ide_debugger_frame_get_depth (self));
+      break;
+
+    case PROP_FILE:
+      g_value_set_string (value, ide_debugger_frame_get_file (self));
+      break;
+
+    case PROP_FUNCTION:
+      g_value_set_string (value, ide_debugger_frame_get_function (self));
+      break;
+
+    case PROP_LIBRARY:
+      g_value_set_string (value, ide_debugger_frame_get_library (self));
+      break;
+
+    case PROP_LINE:
+      g_value_set_uint (value, ide_debugger_frame_get_line (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_frame_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  IdeDebuggerFrame *self = IDE_DEBUGGER_FRAME (object);
+
+  switch (prop_id)
+    {
+    case PROP_ADDRESS:
+      ide_debugger_frame_set_address (self, g_value_get_uint64 (value));
+      break;
+
+    case PROP_ARGS:
+      ide_debugger_frame_set_args (self, g_value_get_boxed (value));
+      break;
+
+    case PROP_DEPTH:
+      ide_debugger_frame_set_depth (self, g_value_get_uint (value));
+      break;
+
+    case PROP_FILE:
+      ide_debugger_frame_set_file (self, g_value_get_string (value));
+      break;
+
+    case PROP_FUNCTION:
+      ide_debugger_frame_set_function (self, g_value_get_string (value));
+      break;
+
+    case PROP_LIBRARY:
+      ide_debugger_frame_set_library (self, g_value_get_string (value));
+      break;
+
+    case PROP_LINE:
+      ide_debugger_frame_set_line (self, g_value_get_uint (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_frame_class_init (IdeDebuggerFrameClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_debugger_frame_finalize;
+  object_class->get_property = ide_debugger_frame_get_property;
+  object_class->set_property = ide_debugger_frame_set_property;
+
+  properties [PROP_ADDRESS] =
+    g_param_spec_uint64 ("address",
+                         "Address",
+                         "Address",
+                         0,
+                         G_MAXUINT64,
+                         IDE_DEBUGGER_ADDRESS_INVALID,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ARGS] =
+    g_param_spec_boxed ("args",
+                        "Args",
+                        "Args",
+                        G_TYPE_STRV,
+                        (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_DEPTH] =
+    g_param_spec_uint ("depth",
+                       "Depth",
+                       "Depth",
+                       0, G_MAXUINT, 0,
+                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_FILE] =
+    g_param_spec_string ("file",
+                         "File",
+                         "The file containing the frame location",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_FUNCTION] =
+    g_param_spec_string ("function",
+                         "Function",
+                         "The function the stack frame represents",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_LIBRARY] =
+    g_param_spec_string ("library",
+                         "Library",
+                         "The library containing the function, if any",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_LINE] =
+    g_param_spec_uint ("line",
+                         "Line",
+                         "Line",
+                         0, G_MAXUINT, 0,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_debugger_frame_init (IdeDebuggerFrame *self)
+{
+}
+
+IdeDebuggerFrame *
+ide_debugger_frame_new (void)
+{
+  return g_object_new (IDE_TYPE_DEBUGGER_FRAME, NULL);
+}
+
+IdeDebuggerAddress
+ide_debugger_frame_get_address (IdeDebuggerFrame *self)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_FRAME (self), IDE_DEBUGGER_ADDRESS_INVALID);
+
+  return priv->address;
+}
+
+void
+ide_debugger_frame_set_address (IdeDebuggerFrame   *self,
+                                IdeDebuggerAddress  address)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_FRAME (self));
+
+  if (priv->address != address)
+    {
+      priv->address = address;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ADDRESS]);
+    }
+}
+
+const gchar * const *
+ide_debugger_frame_get_args (IdeDebuggerFrame *self)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_FRAME (self), NULL);
+
+  return (const gchar * const *)priv->args;
+}
+
+void
+ide_debugger_frame_set_args (IdeDebuggerFrame    *self,
+                             const gchar * const *args)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_FRAME (self));
+
+  if (args != (const gchar * const *)priv->args)
+    {
+      g_strfreev (priv->args);
+      priv->args = g_strdupv ((gchar **)args);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGS]);
+    }
+}
+
+const gchar *
+ide_debugger_frame_get_file (IdeDebuggerFrame *self)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_FRAME (self), NULL);
+
+  return priv->file;
+}
+
+void
+ide_debugger_frame_set_file (IdeDebuggerFrame *self,
+                             const gchar      *file)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_FRAME (self));
+
+  if (g_strcmp0 (priv->file, file) != 0)
+    {
+      g_free (priv->file);
+      priv->file = g_strdup (file);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE]);
+    }
+}
+
+const gchar *
+ide_debugger_frame_get_function (IdeDebuggerFrame *self)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_FRAME (self), NULL);
+
+  return priv->function;
+}
+
+void
+ide_debugger_frame_set_function (IdeDebuggerFrame *self,
+                                 const gchar      *function)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_FRAME (self));
+
+  if (g_strcmp0 (priv->function, function) != 0)
+    {
+      g_free (priv->function);
+      priv->function = g_strdup (function);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FUNCTION]);
+    }
+}
+
+const gchar *
+ide_debugger_frame_get_library (IdeDebuggerFrame *self)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_FRAME (self), NULL);
+
+  return priv->library;
+}
+
+void
+ide_debugger_frame_set_library (IdeDebuggerFrame *self,
+                                const gchar      *library)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_FRAME (self));
+
+  if (g_strcmp0 (priv->library, library) != 0)
+    {
+      g_free (priv->library);
+      priv->library = g_strdup (library);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LIBRARY]);
+    }
+}
+
+guint
+ide_debugger_frame_get_line (IdeDebuggerFrame *self)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_FRAME (self), 0);
+
+  return priv->line;
+}
+
+void
+ide_debugger_frame_set_line (IdeDebuggerFrame *self,
+                             guint             line)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_FRAME (self));
+
+  if (priv->line != line)
+    {
+      priv->line = line;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LINE]);
+    }
+}
+
+guint
+ide_debugger_frame_get_depth (IdeDebuggerFrame *self)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_FRAME (self), 0);
+
+  return priv->depth;
+}
+
+void
+ide_debugger_frame_set_depth (IdeDebuggerFrame *self,
+                              guint             depth)
+{
+  IdeDebuggerFramePrivate *priv = ide_debugger_frame_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_FRAME (self));
+
+  if (priv->depth != depth)
+    {
+      priv->depth = depth;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEPTH]);
+    }
+}
diff --git a/libide/debugger/ide-debugger-frame.h b/libide/debugger/ide-debugger-frame.h
new file mode 100644
index 0000000..37cd65d
--- /dev/null
+++ b/libide/debugger/ide-debugger-frame.h
@@ -0,0 +1,66 @@
+/* ide-debugger-frame.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 IDE_DEBUGGER_FRAME_H
+#define IDE_DEBUGGER_FRAME_H
+
+#include "ide-debugger-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_FRAME (ide_debugger_frame_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeDebuggerFrame, ide_debugger_frame, IDE, DEBUGGER_FRAME, GObject)
+
+struct _IdeDebuggerFrameClass
+{
+  GObjectClass parent;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+};
+
+IdeDebuggerFrame    *ide_debugger_frame_new          (void);
+IdeDebuggerAddress   ide_debugger_frame_get_address  (IdeDebuggerFrame    *self);
+void                 ide_debugger_frame_set_address  (IdeDebuggerFrame    *self,
+                                                      IdeDebuggerAddress   address);
+const gchar         *ide_debugger_frame_get_file     (IdeDebuggerFrame    *self);
+void                 ide_debugger_frame_set_file     (IdeDebuggerFrame    *self,
+                                                      const gchar         *file);
+const gchar         *ide_debugger_frame_get_function (IdeDebuggerFrame    *self);
+void                 ide_debugger_frame_set_function (IdeDebuggerFrame    *self,
+                                                      const gchar         *function);
+const gchar * const *ide_debugger_frame_get_args     (IdeDebuggerFrame    *self);
+void                 ide_debugger_frame_set_args     (IdeDebuggerFrame    *self,
+                                                      const gchar * const *args);
+const gchar         *ide_debugger_frame_get_library  (IdeDebuggerFrame    *self);
+void                 ide_debugger_frame_set_library  (IdeDebuggerFrame    *self,
+                                                      const gchar         *library);
+guint                ide_debugger_frame_get_depth    (IdeDebuggerFrame    *self);
+void                 ide_debugger_frame_set_depth    (IdeDebuggerFrame    *self,
+                                                      guint                depth);
+guint                ide_debugger_frame_get_line     (IdeDebuggerFrame    *self);
+void                 ide_debugger_frame_set_line     (IdeDebuggerFrame    *self,
+                                                      guint                line);
+
+G_END_DECLS
+
+#endif /* IDE_DEBUGGER_FRAME_H */
+
diff --git a/libide/debugger/ide-debugger-instruction.c b/libide/debugger/ide-debugger-instruction.c
new file mode 100644
index 0000000..babfd0b
--- /dev/null
+++ b/libide/debugger/ide-debugger-instruction.c
@@ -0,0 +1,215 @@
+/* ide-debugger-instruction.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 "ide-debugger-instruction"
+
+#include "ide-debugger-instruction.h"
+
+typedef struct
+{
+  IdeDebuggerAddress  address;
+  gchar              *display;
+  gchar              *function;
+} IdeDebuggerInstructionPrivate;
+
+enum {
+  PROP_0,
+  PROP_ADDRESS,
+  PROP_DISPLAY,
+  PROP_FUNCTION,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeDebuggerInstruction, ide_debugger_instruction, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_debugger_instruction_finalize (GObject *object)
+{
+  IdeDebuggerInstruction *self = (IdeDebuggerInstruction *)object;
+  IdeDebuggerInstructionPrivate *priv = ide_debugger_instruction_get_instance_private (self);
+
+  g_clear_pointer (&priv->display, g_free);
+  g_clear_pointer (&priv->function, g_free);
+
+  G_OBJECT_CLASS (ide_debugger_instruction_parent_class)->finalize (object);
+}
+
+static void
+ide_debugger_instruction_get_property (GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+  IdeDebuggerInstruction *self = IDE_DEBUGGER_INSTRUCTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_ADDRESS:
+      g_value_set_uint64 (value, ide_debugger_instruction_get_address (self));
+      break;
+
+    case PROP_DISPLAY:
+      g_value_set_string (value, ide_debugger_instruction_get_display (self));
+      break;
+
+    case PROP_FUNCTION:
+      g_value_set_string (value, ide_debugger_instruction_get_function (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_instruction_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+  IdeDebuggerInstruction *self = IDE_DEBUGGER_INSTRUCTION (object);
+  IdeDebuggerInstructionPrivate *priv = ide_debugger_instruction_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_ADDRESS:
+      priv->address = g_value_get_uint64 (value);
+      break;
+
+    case PROP_DISPLAY:
+      ide_debugger_instruction_set_display (self, g_value_get_string (value));
+      break;
+
+    case PROP_FUNCTION:
+      ide_debugger_instruction_set_function (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_instruction_class_init (IdeDebuggerInstructionClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_debugger_instruction_finalize;
+  object_class->get_property = ide_debugger_instruction_get_property;
+  object_class->set_property = ide_debugger_instruction_set_property;
+
+  properties [PROP_ADDRESS] =
+    g_param_spec_uint64 ("address",
+                         "Address",
+                         "The address of the instruction",
+                         0, G_MAXUINT64, 0,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_DISPLAY] =
+    g_param_spec_string ("display",
+                         "Display",
+                         "Display",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_FUNCTION] =
+    g_param_spec_string ("function",
+                         "Function",
+                         "Function",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_debugger_instruction_init (IdeDebuggerInstruction *self)
+{
+}
+
+IdeDebuggerInstruction *
+ide_debugger_instruction_new (IdeDebuggerAddress address)
+{
+  return g_object_new (IDE_TYPE_DEBUGGER_INSTRUCTION,
+                       "address", address,
+                       NULL);
+}
+
+const gchar *
+ide_debugger_instruction_get_display (IdeDebuggerInstruction *self)
+{
+  IdeDebuggerInstructionPrivate *priv = ide_debugger_instruction_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_INSTRUCTION (self), NULL);
+
+  return priv->display;
+}
+
+void
+ide_debugger_instruction_set_display (IdeDebuggerInstruction *self,
+                                      const gchar            *display)
+{
+  IdeDebuggerInstructionPrivate *priv = ide_debugger_instruction_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_INSTRUCTION (self));
+
+  if (g_strcmp0 (priv->display, display) != 0)
+    {
+      g_free (priv->display);
+      priv->display = g_strdup (display);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISPLAY]);
+    }
+}
+
+const gchar *
+ide_debugger_instruction_get_function (IdeDebuggerInstruction *self)
+{
+  IdeDebuggerInstructionPrivate *priv = ide_debugger_instruction_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_INSTRUCTION (self), NULL);
+
+  return priv->function;
+}
+
+void
+ide_debugger_instruction_set_function (IdeDebuggerInstruction *self,
+                                      const gchar            *function)
+{
+  IdeDebuggerInstructionPrivate *priv = ide_debugger_instruction_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_INSTRUCTION (self));
+
+  if (g_strcmp0 (priv->function, function) != 0)
+    {
+      g_free (priv->function);
+      priv->function = g_strdup (function);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FUNCTION]);
+    }
+}
+
+IdeDebuggerAddress
+ide_debugger_instruction_get_address (IdeDebuggerInstruction *self)
+{
+  IdeDebuggerInstructionPrivate *priv = ide_debugger_instruction_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_INSTRUCTION (self), 0);
+
+  return priv->address;
+}
diff --git a/libide/debugger/ide-debugger-instruction.h b/libide/debugger/ide-debugger-instruction.h
new file mode 100644
index 0000000..b0bbe46
--- /dev/null
+++ b/libide/debugger/ide-debugger-instruction.h
@@ -0,0 +1,48 @@
+/* ide-debugger-instruction.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/>.
+ */
+
+#pragma once
+
+#include "ide-debugger-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_INSTRUCTION (ide_debugger_instruction_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeDebuggerInstruction, ide_debugger_instruction, IDE, DEBUGGER_INSTRUCTION, 
GObject)
+
+struct _IdeDebuggerInstructionClass
+{
+  GObjectClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+};
+
+IdeDebuggerInstruction *ide_debugger_instruction_new          (IdeDebuggerAddress      address);
+IdeDebuggerAddress      ide_debugger_instruction_get_address  (IdeDebuggerInstruction *self);
+const gchar            *ide_debugger_instruction_get_function (IdeDebuggerInstruction *self);
+void                    ide_debugger_instruction_set_function (IdeDebuggerInstruction *self,
+                                                               const gchar            *function);
+const gchar            *ide_debugger_instruction_get_display  (IdeDebuggerInstruction *self);
+void                    ide_debugger_instruction_set_display  (IdeDebuggerInstruction *self,
+                                                               const gchar            *display);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-libraries-view.c b/libide/debugger/ide-debugger-libraries-view.c
new file mode 100644
index 0000000..fa97e61
--- /dev/null
+++ b/libide/debugger/ide-debugger-libraries-view.c
@@ -0,0 +1,363 @@
+/* ide-debugger-libraries-view.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 "ide-debugger-libraries-view"
+
+#include <dazzle.h>
+
+#include "ide-debugger-libraries-view.h"
+
+struct _IdeDebuggerLibrariesView
+{
+  GtkBin parent_instance;
+
+  /* Template widgets */
+  GtkTreeView         *tree_view;
+  GtkListStore        *list_store;
+  GtkCellRendererText *range_cell;
+  GtkTreeViewColumn   *range_column;
+  GtkCellRendererText *target_cell;
+  GtkTreeViewColumn   *target_column;
+
+  /* Onwed refnerences */
+  DzlSignalGroup *debugger_signals;
+};
+
+enum {
+  PROP_0,
+  PROP_DEBUGGER,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (IdeDebuggerLibrariesView, ide_debugger_libraries_view, GTK_TYPE_BIN)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_debugger_libraries_view_bind (IdeDebuggerLibrariesView *self,
+                                  IdeDebugger              *debugger,
+                                  DzlSignalGroup           *signals)
+{
+  g_assert (IDE_IS_DEBUGGER_LIBRARIES_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->tree_view),
+                            !ide_debugger_get_is_running (debugger));
+}
+
+static void
+ide_debugger_libraries_view_unbind (IdeDebuggerLibrariesView *self,
+                                    DzlSignalGroup           *signals)
+{
+  g_assert (IDE_IS_DEBUGGER_LIBRARIES_VIEW (self));
+  g_assert (DZL_IS_SIGNAL_GROUP (signals));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->tree_view), FALSE);
+}
+
+static void
+ide_debugger_libraries_view_running (IdeDebuggerLibrariesView *self,
+                                     IdeDebugger              *debugger)
+{
+  g_assert (IDE_IS_DEBUGGER_LIBRARIES_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->tree_view), FALSE);
+}
+
+static void
+ide_debugger_libraries_view_stopped (IdeDebuggerLibrariesView *self,
+                                     IdeDebuggerStopReason     stop_reason,
+                                     IdeDebuggerBreakpoint    *breakpoint,
+                                     IdeDebugger              *debugger)
+{
+  g_assert (IDE_IS_DEBUGGER_LIBRARIES_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->tree_view), TRUE);
+}
+
+static void
+ide_debugger_libraries_view_library_loaded (IdeDebuggerLibrariesView *self,
+                                            IdeDebuggerLibrary       *library,
+                                            IdeDebugger              *debugger)
+{
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_LIBRARIES_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_LIBRARY (library));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  dzl_gtk_list_store_insert_sorted (self->list_store,
+                                    &iter, library, 0,
+                                    (GCompareDataFunc)ide_debugger_library_compare,
+                                    NULL);
+
+  gtk_list_store_set (self->list_store, &iter, 0, library, -1);
+}
+
+static void
+ide_debugger_libraries_view_library_unloaded (IdeDebuggerLibrariesView *self,
+                                              IdeDebuggerLibrary       *library,
+                                              IdeDebugger              *debugger)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_LIBRARIES_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_LIBRARY (library));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  model = GTK_TREE_MODEL (self->list_store);
+
+  if (gtk_tree_model_get_iter_first (model, &iter))
+    {
+      do
+        {
+          g_autoptr(IdeDebuggerLibrary) element = NULL;
+
+          gtk_tree_model_get (model, &iter, 0, &element, -1);
+
+          if (ide_debugger_library_compare (library, element) == 0)
+            {
+              gtk_list_store_remove (self->list_store, &iter);
+              break;
+            }
+        }
+      while (gtk_tree_model_iter_next (model, &iter));
+    }
+}
+
+static void
+range_cell_data_func (GtkCellLayout   *cell_layout,
+                      GtkCellRenderer *cell,
+                      GtkTreeModel    *model,
+                      GtkTreeIter     *iter,
+                      gpointer         user_data)
+{
+  g_autoptr(IdeDebuggerLibrary) library = NULL;
+  g_autofree gchar *str = NULL;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+
+  gtk_tree_model_get (model, iter, 0, &library, -1);
+
+  if (library != NULL)
+    {
+      GPtrArray *ranges = ide_debugger_library_get_ranges (library);
+
+      if (ranges != NULL && ranges->len > 0)
+        {
+          IdeDebuggerAddressRange *range = g_ptr_array_index (ranges, 0);
+
+          str = g_strdup_printf ("0x%"G_GINT64_MODIFIER"x - 0x%"G_GINT64_MODIFIER"x",
+                                 range->from, range->to);
+        }
+    }
+
+  g_object_set (cell, "text", str, NULL);
+}
+
+static void
+string_property_cell_data_func (GtkCellLayout   *cell_layout,
+                                GtkCellRenderer *cell,
+                                GtkTreeModel    *model,
+                                GtkTreeIter     *iter,
+                                gpointer         user_data)
+{
+  const gchar *property = user_data;
+  g_autoptr(GObject) object = NULL;
+  g_auto(GValue) value = G_VALUE_INIT;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+  g_assert (property != NULL);
+
+  g_value_init (&value, G_TYPE_STRING);
+  gtk_tree_model_get (model, iter, 0, &object, -1);
+
+  if (object != NULL)
+    g_object_get_property (object, property, &value);
+
+  g_object_set_property (G_OBJECT (cell), "text", &value);
+}
+
+static void
+ide_debugger_libraries_view_destroy (GtkWidget *widget)
+{
+  IdeDebuggerLibrariesView *self = (IdeDebuggerLibrariesView *)widget;
+
+  g_clear_object (&self->debugger_signals);
+
+  GTK_WIDGET_CLASS (ide_debugger_libraries_view_parent_class)->destroy (widget);
+}
+
+static void
+ide_debugger_libraries_view_get_property (GObject    *object,
+                                          guint       prop_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  IdeDebuggerLibrariesView *self = IDE_DEBUGGER_LIBRARIES_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEBUGGER:
+      g_value_set_object (value, ide_debugger_libraries_view_get_debugger (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_libraries_view_set_property (GObject      *object,
+                                          guint         prop_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  IdeDebuggerLibrariesView *self = IDE_DEBUGGER_LIBRARIES_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEBUGGER:
+      ide_debugger_libraries_view_set_debugger (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_libraries_view_class_init (IdeDebuggerLibrariesViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = ide_debugger_libraries_view_get_property;
+  object_class->set_property = ide_debugger_libraries_view_set_property;
+
+  widget_class->destroy = ide_debugger_libraries_view_destroy;
+
+  properties [PROP_DEBUGGER] =
+    g_param_spec_object ("debugger",
+                         "Debugger",
+                         "The debugger instance",
+                         IDE_TYPE_DEBUGGER,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-debugger-libraries-view.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLibrariesView, tree_view);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLibrariesView, list_store);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLibrariesView, target_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLibrariesView, target_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLibrariesView, range_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLibrariesView, range_column);
+
+  g_type_ensure (IDE_TYPE_DEBUGGER_LIBRARY);
+}
+
+static void
+ide_debugger_libraries_view_init (IdeDebuggerLibrariesView *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->debugger_signals = dzl_signal_group_new (IDE_TYPE_DEBUGGER);
+
+  g_signal_connect_swapped (self->debugger_signals,
+                            "bind",
+                            G_CALLBACK (ide_debugger_libraries_view_bind),
+                            self);
+
+  g_signal_connect_swapped (self->debugger_signals,
+                            "unbind",
+                            G_CALLBACK (ide_debugger_libraries_view_unbind),
+                            self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "running",
+                                    G_CALLBACK (ide_debugger_libraries_view_running),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "stopped",
+                                    G_CALLBACK (ide_debugger_libraries_view_stopped),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "library-loaded",
+                                    G_CALLBACK (ide_debugger_libraries_view_library_loaded),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "library-unloaded",
+                                    G_CALLBACK (ide_debugger_libraries_view_library_unloaded),
+                                    self);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->target_column),
+                                      GTK_CELL_RENDERER (self->target_cell),
+                                      string_property_cell_data_func, "target-name", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->range_column),
+                                      GTK_CELL_RENDERER (self->range_cell),
+                                      range_cell_data_func, NULL, NULL);
+}
+
+GtkWidget *
+ide_debugger_libraries_view_new (void)
+{
+  return g_object_new (IDE_TYPE_DEBUGGER_LIBRARIES_VIEW, NULL);
+}
+
+/**
+ * ide_debugger_libraries_view_get_debugger:
+ * @self: a #IdeDebuggerLibrariesView
+ *
+ * Gets the debugger property.
+ *
+ * Returns: (transfer none): An #IdeDebugger or %NULL.
+ */
+IdeDebugger *
+ide_debugger_libraries_view_get_debugger (IdeDebuggerLibrariesView *self)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER_LIBRARIES_VIEW (self), NULL);
+
+  if (self->debugger_signals != NULL)
+    return dzl_signal_group_get_target (self->debugger_signals);
+  return NULL;
+}
+
+void
+ide_debugger_libraries_view_set_debugger (IdeDebuggerLibrariesView *self,
+                                          IdeDebugger              *debugger)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER_LIBRARIES_VIEW (self));
+  g_return_if_fail (!debugger || IDE_IS_DEBUGGER (debugger));
+
+  dzl_signal_group_set_target (self->debugger_signals, debugger);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEBUGGER]);
+}
diff --git a/libide/debugger/ide-debugger-libraries-view.h b/libide/debugger/ide-debugger-libraries-view.h
new file mode 100644
index 0000000..7dadc07
--- /dev/null
+++ b/libide/debugger/ide-debugger-libraries-view.h
@@ -0,0 +1,36 @@
+/* ide-debugger-libraries-view.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/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "ide-debugger.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_LIBRARIES_VIEW (ide_debugger_libraries_view_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDebuggerLibrariesView, ide_debugger_libraries_view, IDE, DEBUGGER_LIBRARIES_VIEW, 
GtkBin)
+
+GtkWidget   *ide_debugger_libraries_view_new          (void);
+IdeDebugger *ide_debugger_libraries_view_get_debugger (IdeDebuggerLibrariesView *self);
+void         ide_debugger_libraries_view_set_debugger (IdeDebuggerLibrariesView *self,
+                                                       IdeDebugger              *debugger);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-library.c b/libide/debugger/ide-debugger-library.c
new file mode 100644
index 0000000..2db2e0e
--- /dev/null
+++ b/libide/debugger/ide-debugger-library.c
@@ -0,0 +1,275 @@
+/* ide-debugger-library.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 "ide-debugger-library"
+
+#include "ide-debugger-library.h"
+
+typedef struct
+{
+  gchar *id;
+  gchar *host_name;
+  gchar *target_name;
+  GPtrArray *ranges;
+} IdeDebuggerLibraryPrivate;
+
+enum {
+  PROP_0,
+  PROP_ID,
+  PROP_HOST_NAME,
+  PROP_TARGET_NAME,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeDebuggerLibrary, ide_debugger_library, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_debugger_library_finalize (GObject *object)
+{
+  IdeDebuggerLibrary *self = (IdeDebuggerLibrary *)object;
+  IdeDebuggerLibraryPrivate *priv = ide_debugger_library_get_instance_private (self);
+
+  g_clear_pointer (&priv->id, g_free);
+  g_clear_pointer (&priv->host_name, g_free);
+  g_clear_pointer (&priv->ranges, g_ptr_array_unref);
+  g_clear_pointer (&priv->target_name, g_free);
+
+  G_OBJECT_CLASS (ide_debugger_library_parent_class)->finalize (object);
+}
+
+static void
+ide_debugger_library_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  IdeDebuggerLibrary *self = IDE_DEBUGGER_LIBRARY (object);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      g_value_set_string (value, ide_debugger_library_get_id (self));
+      break;
+
+    case PROP_HOST_NAME:
+      g_value_set_string (value, ide_debugger_library_get_host_name (self));
+      break;
+
+    case PROP_TARGET_NAME:
+      g_value_set_string (value, ide_debugger_library_get_target_name (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_library_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  IdeDebuggerLibrary *self = IDE_DEBUGGER_LIBRARY (object);
+  IdeDebuggerLibraryPrivate *priv = ide_debugger_library_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      priv->id = g_value_dup_string (value);
+      break;
+
+    case PROP_HOST_NAME:
+      ide_debugger_library_set_host_name (self, g_value_get_string (value));
+      break;
+
+    case PROP_TARGET_NAME:
+      ide_debugger_library_set_target_name (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_library_class_init (IdeDebuggerLibraryClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_debugger_library_finalize;
+  object_class->get_property = ide_debugger_library_get_property;
+  object_class->set_property = ide_debugger_library_set_property;
+
+  properties [PROP_ID] =
+    g_param_spec_string ("id",
+                         "Id",
+                         "The identifier for library",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_HOST_NAME] =
+    g_param_spec_string ("host-name",
+                         "Host Name",
+                         "The host name for the library",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TARGET_NAME] =
+    g_param_spec_string ("target-name",
+                         "Target Name",
+                         "The target name for the library",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_debugger_library_init (IdeDebuggerLibrary *self)
+{
+  IdeDebuggerLibraryPrivate *priv = ide_debugger_library_get_instance_private (self);
+
+  priv->ranges = g_ptr_array_new_with_free_func ((GDestroyNotify)ide_debugger_address_range_free);
+}
+
+IdeDebuggerLibrary *
+ide_debugger_library_new (const gchar *id)
+{
+  return g_object_new (IDE_TYPE_DEBUGGER_LIBRARY,
+                       "id", id,
+                       NULL);
+}
+
+const gchar *
+ide_debugger_library_get_id (IdeDebuggerLibrary *self)
+{
+  IdeDebuggerLibraryPrivate *priv = ide_debugger_library_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_LIBRARY (self), NULL);
+
+  return priv->id;
+}
+
+const gchar *
+ide_debugger_library_get_host_name (IdeDebuggerLibrary *self)
+{
+  IdeDebuggerLibraryPrivate *priv = ide_debugger_library_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_LIBRARY (self), NULL);
+
+  return priv->host_name;
+}
+
+void
+ide_debugger_library_set_host_name (IdeDebuggerLibrary *self,
+                                    const gchar        *host_name)
+{
+  IdeDebuggerLibraryPrivate *priv = ide_debugger_library_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_LIBRARY (self));
+
+  if (g_strcmp0 (priv->host_name, host_name) != 0)
+    {
+      g_free (priv->host_name);
+      priv->host_name = g_strdup (host_name);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HOST_NAME]);
+    }
+}
+
+const gchar *
+ide_debugger_library_get_target_name (IdeDebuggerLibrary *self)
+{
+  IdeDebuggerLibraryPrivate *priv = ide_debugger_library_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_LIBRARY (self), NULL);
+
+  return priv->target_name;
+}
+
+void
+ide_debugger_library_set_target_name (IdeDebuggerLibrary *self,
+                                      const gchar        *target_name)
+{
+  IdeDebuggerLibraryPrivate *priv = ide_debugger_library_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_LIBRARY (self));
+
+  if (g_strcmp0 (priv->target_name, target_name) != 0)
+    {
+      g_free (priv->target_name);
+      priv->target_name = g_strdup (target_name);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TARGET_NAME]);
+    }
+}
+
+/**
+ * ide_debugger_library_get_ranges:
+ * @self: An #IdeDebuggerLibrary
+ *
+ * Gets the list of address ranges for the library.
+ *
+ * Returns: (transfer none) (element-type Ide.DebuggerAddressRange): A #GPtrArray
+ *   containing the list of address ranges.
+ */
+GPtrArray *
+ide_debugger_library_get_ranges (IdeDebuggerLibrary *self)
+{
+  IdeDebuggerLibraryPrivate *priv = ide_debugger_library_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_LIBRARY (self), NULL);
+
+  return priv->ranges;
+}
+
+/**
+ * ide_debugger_library_add_range:
+ * @self: An #IdeDebuggerLibrary
+ * @range: the address range of the library
+ *
+ * Adds @range to the list of ranges for which the library is mapped in
+ * the inferior's address space.
+ */
+void
+ide_debugger_library_add_range (IdeDebuggerLibrary            *self,
+                                const IdeDebuggerAddressRange *range)
+{
+  IdeDebuggerLibraryPrivate *priv = ide_debugger_library_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_LIBRARY (self));
+  g_return_if_fail (range != NULL);
+
+  /* NOTE: It is unclear to me if a single library can have different
+   *       ELF sections from one library mapped into different, non-contiguous
+   *       regions within the inferior's address space.
+   */
+
+  g_ptr_array_add (priv->ranges, ide_debugger_address_range_copy (range));
+}
+
+gint
+ide_debugger_library_compare (IdeDebuggerLibrary *a,
+                              IdeDebuggerLibrary *b)
+{
+  IdeDebuggerLibraryPrivate *priv_a = ide_debugger_library_get_instance_private (a);
+  IdeDebuggerLibraryPrivate *priv_b = ide_debugger_library_get_instance_private (b);
+
+  return g_strcmp0 (priv_a->id, priv_b->id);
+}
diff --git a/libide/debugger/ide-debugger-library.h b/libide/debugger/ide-debugger-library.h
new file mode 100644
index 0000000..ddd082c
--- /dev/null
+++ b/libide/debugger/ide-debugger-library.h
@@ -0,0 +1,59 @@
+/* ide-debugger-library.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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+#include "ide-debugger-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_LIBRARY (ide_debugger_library_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeDebuggerLibrary, ide_debugger_library, IDE, DEBUGGER_LIBRARY, GObject)
+
+struct _IdeDebuggerLibraryClass
+{
+  GObjectClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+gint                ide_debugger_library_compare         (IdeDebuggerLibrary            *a,
+                                                          IdeDebuggerLibrary            *b);
+IdeDebuggerLibrary *ide_debugger_library_new             (const gchar                   *id);
+const gchar        *ide_debugger_library_get_id          (IdeDebuggerLibrary            *self);
+GPtrArray          *ide_debugger_library_get_ranges      (IdeDebuggerLibrary            *self);
+void                ide_debugger_library_add_range       (IdeDebuggerLibrary            *self,
+                                                          const IdeDebuggerAddressRange *range);
+const gchar        *ide_debugger_library_get_host_name   (IdeDebuggerLibrary            *self);
+void                ide_debugger_library_set_host_name   (IdeDebuggerLibrary            *self,
+                                                          const gchar                   *host_name);
+const gchar        *ide_debugger_library_get_target_name (IdeDebuggerLibrary            *self);
+void                ide_debugger_library_set_target_name (IdeDebuggerLibrary            *self,
+                                                          const gchar                   *target_name);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-locals-view.c b/libide/debugger/ide-debugger-locals-view.c
new file mode 100644
index 0000000..64c645b
--- /dev/null
+++ b/libide/debugger/ide-debugger-locals-view.c
@@ -0,0 +1,433 @@
+/* ide-debugger-locals-view.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 "ide-debugger-locals-view"
+
+#include <dazzle.h>
+#include <glib/gi18n.h>
+
+#include "ide-debugger-locals-view.h"
+
+struct _IdeDebuggerLocalsView
+{
+  GtkBin          parent_instance;
+
+  /* Owned references */
+  DzlSignalGroup *debugger_signals;
+
+  /* Template references */
+  GtkTreeStore        *tree_store;
+  GtkTreeView         *tree_view;
+  GtkTreeViewColumn   *type_column;
+  GtkCellRendererText *type_cell;
+  GtkTreeViewColumn   *variable_column;
+  GtkCellRendererText *variable_cell;
+  GtkTreeViewColumn   *value_column;
+  GtkCellRendererText *value_cell;
+};
+
+enum {
+  PROP_0,
+  PROP_DEBUGGER,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (IdeDebuggerLocalsView, ide_debugger_locals_view, GTK_TYPE_BIN)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_debugger_locals_view_running (IdeDebuggerLocalsView *self,
+                                  IdeDebugger           *debugger)
+{
+  g_assert (IDE_IS_DEBUGGER_LOCALS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->tree_view), FALSE);
+  gtk_tree_store_clear (self->tree_store);
+}
+
+static void
+ide_debugger_locals_view_stopped (IdeDebuggerLocalsView *self,
+                                  IdeDebuggerStopReason  stop_reason,
+                                  IdeDebuggerBreakpoint *breakpoint,
+                                  IdeDebugger           *debugger)
+{
+  g_assert (IDE_IS_DEBUGGER_LOCALS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_STOP_REASON (stop_reason));
+  g_assert (!breakpoint || IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->tree_view), TRUE);
+}
+
+static void
+name_cell_data_func (GtkCellLayout   *cell_layout,
+                     GtkCellRenderer *cell,
+                     GtkTreeModel    *model,
+                     GtkTreeIter     *iter,
+                     gpointer         user_data)
+{
+  g_autoptr(IdeDebuggerVariable) var = NULL;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+
+  gtk_tree_model_get (model, iter, 0, &var, -1);
+
+  if (var != NULL)
+    {
+      g_object_set (cell,
+                    "text", ide_debugger_variable_get_name (var),
+                    NULL);
+    }
+  else
+    {
+      g_autofree gchar *str = NULL;
+
+      gtk_tree_model_get (model, iter, 1, &str, -1);
+      g_object_set (cell, "text", str, NULL);
+    }
+}
+
+static void
+string_property_cell_data_func (GtkCellLayout   *cell_layout,
+                                GtkCellRenderer *cell,
+                                GtkTreeModel    *model,
+                                GtkTreeIter     *iter,
+                                gpointer         user_data)
+{
+  const gchar *property = user_data;
+  g_autoptr(GObject) object = NULL;
+  g_auto(GValue) value = G_VALUE_INIT;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+  g_assert (property != NULL);
+
+  g_value_init (&value, G_TYPE_STRING);
+  gtk_tree_model_get (model, iter, 0, &object, -1);
+
+  if (object != NULL)
+    g_object_get_property (object, property, &value);
+
+  g_object_set_property (G_OBJECT (cell), "text", &value);
+}
+
+static void
+ide_debugger_locals_view_finalize (GObject *object)
+{
+  IdeDebuggerLocalsView *self = (IdeDebuggerLocalsView *)object;
+
+  g_clear_object (&self->debugger_signals);
+
+  G_OBJECT_CLASS (ide_debugger_locals_view_parent_class)->finalize (object);
+}
+
+static void
+ide_debugger_locals_view_get_property (GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+  IdeDebuggerLocalsView *self = IDE_DEBUGGER_LOCALS_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEBUGGER:
+      g_value_set_object (value, ide_debugger_locals_view_get_debugger (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_locals_view_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+  IdeDebuggerLocalsView *self = IDE_DEBUGGER_LOCALS_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEBUGGER:
+      ide_debugger_locals_view_set_debugger (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_locals_view_class_init (IdeDebuggerLocalsViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = ide_debugger_locals_view_finalize;
+  object_class->get_property = ide_debugger_locals_view_get_property;
+  object_class->set_property = ide_debugger_locals_view_set_property;
+
+  properties [PROP_DEBUGGER] =
+    g_param_spec_object ("debugger",
+                         "Debugger",
+                         "The debugger instance",
+                         IDE_TYPE_DEBUGGER,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-debugger-locals-view.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLocalsView, tree_store);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLocalsView, tree_view);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLocalsView, type_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLocalsView, type_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLocalsView, value_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLocalsView, value_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLocalsView, variable_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerLocalsView, variable_column);
+}
+
+static void
+ide_debugger_locals_view_init (IdeDebuggerLocalsView *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->debugger_signals = dzl_signal_group_new (IDE_TYPE_DEBUGGER);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "running",
+                                    G_CALLBACK (ide_debugger_locals_view_running),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "stopped",
+                                    G_CALLBACK (ide_debugger_locals_view_stopped),
+                                    self);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->variable_column),
+                                      GTK_CELL_RENDERER (self->variable_cell),
+                                      name_cell_data_func, NULL, NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->type_column),
+                                      GTK_CELL_RENDERER (self->type_cell),
+                                      string_property_cell_data_func, "type-name", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->value_column),
+                                      GTK_CELL_RENDERER (self->value_cell),
+                                      string_property_cell_data_func, "value", NULL);
+}
+
+GtkWidget *
+ide_debugger_locals_view_new (void)
+{
+  return g_object_new (IDE_TYPE_DEBUGGER_LOCALS_VIEW, NULL);
+}
+
+/**
+ * ide_debugger_locals_view_get_debugger:
+ * @self: a #IdeDebuggerLocalsView
+ *
+ * Gets the debugger instance.
+ *
+ * Returns: (transfer none): An #IdeDebugger
+ *
+ * Since: 3.26
+ */
+IdeDebugger *
+ide_debugger_locals_view_get_debugger (IdeDebuggerLocalsView *self)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER_LOCALS_VIEW (self), NULL);
+
+  return dzl_signal_group_get_target (self->debugger_signals);
+}
+
+void
+ide_debugger_locals_view_set_debugger (IdeDebuggerLocalsView *self,
+                                       IdeDebugger           *debugger)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER_LOCALS_VIEW (self));
+  g_return_if_fail (!debugger || IDE_IS_DEBUGGER (debugger));
+
+  dzl_signal_group_set_target (self->debugger_signals, debugger);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEBUGGER]);
+}
+
+static void
+ide_debugger_locals_view_load_locals_cb (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
+{
+  IdeDebuggerLocalsView *self;
+  IdeDebugger *debugger = (IdeDebugger *)object;
+  g_autoptr(GPtrArray) locals = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GTask) task = user_data;
+  GtkTreeIter parent;
+
+  g_assert (IDE_IS_DEBUGGER (debugger));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  locals = ide_debugger_list_locals_finish (debugger, result, &error);
+
+  if (locals == NULL)
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  self = g_task_get_source_object (task);
+  g_assert (IDE_IS_DEBUGGER_LOCALS_VIEW (self));
+
+  gtk_tree_store_append (self->tree_store, &parent, NULL);
+  gtk_tree_store_set (self->tree_store, &parent, 1, _("Locals"), -1);
+
+  for (guint i = 0; i < locals->len; i++)
+    {
+      IdeDebuggerVariable *var = g_ptr_array_index (locals, i);
+      GtkTreeIter iter;
+
+      gtk_tree_store_append (self->tree_store, &iter, &parent);
+      gtk_tree_store_set (self->tree_store, &iter, 0, var, -1);
+
+      /* Add a deummy row that we can backfill when the user requests
+       * that the variable is expanded.
+       */
+      if (ide_debugger_variable_get_has_children (var))
+        {
+          GtkTreeIter dummy;
+
+          gtk_tree_store_append (self->tree_store, &dummy, &iter);
+        }
+    }
+
+  gtk_tree_view_expand_all (self->tree_view);
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_debugger_locals_view_load_params_cb (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
+{
+  g_autoptr(IdeDebuggerLocalsView) self = user_data;
+  IdeDebugger *debugger = (IdeDebugger *)object;
+  g_autoptr(GPtrArray) params = NULL;
+  g_autoptr(GError) error = NULL;
+  GtkTreeIter parent;
+
+  g_assert (IDE_IS_DEBUGGER (debugger));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_DEBUGGER_LOCALS_VIEW (self));
+
+  params = ide_debugger_list_params_finish (debugger, result, &error);
+
+  if (params == NULL)
+    {
+      g_warning ("%s", error->message);
+      return;
+    }
+
+  gtk_tree_store_append (self->tree_store, &parent, NULL);
+  gtk_tree_store_set (self->tree_store, &parent, 1, _("Parameters"), -1);
+
+  for (guint i = 0; i < params->len; i++)
+    {
+      IdeDebuggerVariable *var = g_ptr_array_index (params, i);
+      GtkTreeIter iter;
+
+      gtk_tree_store_append (self->tree_store, &iter, &parent);
+      gtk_tree_store_set (self->tree_store, &iter, 0, var, -1);
+
+      /* Add a deummy row that we can backfill when the user requests
+       * that the variable is expanded.
+       */
+      if (ide_debugger_variable_get_has_children (var))
+        {
+          GtkTreeIter dummy;
+
+          gtk_tree_store_append (self->tree_store, &dummy, &iter);
+        }
+    }
+}
+
+void
+ide_debugger_locals_view_load_async (IdeDebuggerLocalsView *self,
+                                     IdeDebuggerThread     *thread,
+                                     IdeDebuggerFrame      *frame,
+                                     GCancellable          *cancellable,
+                                     GAsyncReadyCallback    callback,
+                                     gpointer               user_data)
+{
+  IdeDebugger *debugger;
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (IDE_IS_DEBUGGER_LOCALS_VIEW (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD (thread));
+  g_return_if_fail (IDE_IS_DEBUGGER_FRAME (frame));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  gtk_tree_store_clear (self->tree_store);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+  g_task_set_source_tag (task, ide_debugger_locals_view_load_async);
+
+  debugger = ide_debugger_locals_view_get_debugger (self);
+
+  if (debugger == NULL)
+    {
+      g_task_return_boolean (task, TRUE);
+      return;
+    }
+
+  ide_debugger_list_params_async (debugger,
+                                  thread,
+                                  frame,
+                                  cancellable,
+                                  ide_debugger_locals_view_load_params_cb,
+                                  g_object_ref (self));
+
+  ide_debugger_list_locals_async (debugger,
+                                  thread,
+                                  frame,
+                                  cancellable,
+                                  ide_debugger_locals_view_load_locals_cb,
+                                  g_steal_pointer (&task));
+}
+
+gboolean
+ide_debugger_locals_view_load_finish (IdeDebuggerLocalsView  *self,
+                                      GAsyncResult           *result,
+                                      GError                **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER_LOCALS_VIEW (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/libide/debugger/ide-debugger-locals-view.h b/libide/debugger/ide-debugger-locals-view.h
new file mode 100644
index 0000000..5427a97
--- /dev/null
+++ b/libide/debugger/ide-debugger-locals-view.h
@@ -0,0 +1,45 @@
+/* ide-debugger-locals-view.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/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "ide-debugger.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_LOCALS_VIEW (ide_debugger_locals_view_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDebuggerLocalsView, ide_debugger_locals_view, IDE, DEBUGGER_LOCALS_VIEW, GtkBin)
+
+GtkWidget   *ide_debugger_locals_view_new          (void);
+IdeDebugger *ide_debugger_locals_view_get_debugger (IdeDebuggerLocalsView  *self);
+void         ide_debugger_locals_view_set_debugger (IdeDebuggerLocalsView  *self,
+                                                    IdeDebugger            *debugger);
+void         ide_debugger_locals_view_load_async   (IdeDebuggerLocalsView  *self,
+                                                    IdeDebuggerThread      *thread,
+                                                    IdeDebuggerFrame       *frame,
+                                                    GCancellable           *cancellable,
+                                                    GAsyncReadyCallback     callback,
+                                                    gpointer                user_data);
+gboolean     ide_debugger_locals_view_load_finish  (IdeDebuggerLocalsView  *self,
+                                                    GAsyncResult           *result,
+                                                    GError                **error);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-private.h b/libide/debugger/ide-debugger-private.h
new file mode 100644
index 0000000..3641bf3
--- /dev/null
+++ b/libide/debugger/ide-debugger-private.h
@@ -0,0 +1,95 @@
+/* ide-debugger-private.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/>.
+ */
+
+#pragma once
+
+#include "ide-debugger.h"
+
+G_BEGIN_DECLS
+
+void       _ide_debugger_class_init_actions            (GActionGroupInterface          *iface);
+void       _ide_debugger_update_actions                (IdeDebugger                    *self);
+gboolean   _ide_debugger_get_has_started               (IdeDebugger                    *self);
+void       _ide_debugger_real_list_frames_async        (IdeDebugger                    *self,
+                                                        IdeDebuggerThread              *thread,
+                                                        GCancellable                   *cancellable,
+                                                        GAsyncReadyCallback             callback,
+                                                        gpointer                        user_data);
+GPtrArray *_ide_debugger_real_list_frames_finish       (IdeDebugger                    *self,
+                                                        GAsyncResult                   *result,
+                                                        GError                        **error);
+void       _ide_debugger_real_interrupt_async          (IdeDebugger                    *self,
+                                                        IdeDebuggerThreadGroup         *thread_group,
+                                                        GCancellable                   *cancellable,
+                                                        GAsyncReadyCallback             callback,
+                                                        gpointer                        user_data);
+gboolean   _ide_debugger_real_interrupt_finish         (IdeDebugger                    *self,
+                                                        GAsyncResult                   *result,
+                                                        GError                        **error);
+void       _ide_debugger_real_send_signal_async        (IdeDebugger                    *self,
+                                                        gint                            signum,
+                                                        GCancellable                   *cancellable,
+                                                        GAsyncReadyCallback             callback,
+                                                        gpointer                        user_data);
+gboolean   _ide_debugger_real_send_signal_finish       (IdeDebugger                    *self,
+                                                        GAsyncResult                   *result,
+                                                        GError                        **error);
+void       _ide_debugger_real_modify_breakpoint_async  (IdeDebugger                    *self,
+                                                        IdeDebuggerBreakpointChange     change,
+                                                        IdeDebuggerBreakpoint          *breakpoint,
+                                                        GCancellable                   *cancellable,
+                                                        GAsyncReadyCallback             callback,
+                                                        gpointer                        user_data);
+gboolean   _ide_debugger_real_modify_breakpoint_finish (IdeDebugger                    *self,
+                                                        GAsyncResult                   *result,
+                                                        GError                        **error);
+void       _ide_debugger_real_list_params_async        (IdeDebugger                    *self,
+                                                        IdeDebuggerThread              *thread,
+                                                        IdeDebuggerFrame               *frame,
+                                                        GCancellable                   *cancellable,
+                                                        GAsyncReadyCallback             callback,
+                                                        gpointer                        user_data);
+GPtrArray *_ide_debugger_real_list_params_finish       (IdeDebugger                    *self,
+                                                        GAsyncResult                   *result,
+                                                        GError                        **error);
+void       _ide_debugger_real_list_locals_async        (IdeDebugger                    *self,
+                                                        IdeDebuggerThread              *thread,
+                                                        IdeDebuggerFrame               *frame,
+                                                        GCancellable                   *cancellable,
+                                                        GAsyncReadyCallback             callback,
+                                                        gpointer                        user_data);
+GPtrArray *_ide_debugger_real_list_locals_finish       (IdeDebugger                    *self,
+                                                        GAsyncResult                   *result,
+                                                        GError                        **error);
+void       _ide_debugger_real_list_registers_async     (IdeDebugger                    *self,
+                                                        GCancellable                   *cancellable,
+                                                        GAsyncReadyCallback             callback,
+                                                        gpointer                        user_data);
+GPtrArray *_ide_debugger_real_list_registers_finish    (IdeDebugger                    *self,
+                                                        GAsyncResult                   *result,
+                                                        GError                        **error);
+void       _ide_debugger_real_disassemble_async        (IdeDebugger                    *self,
+                                                        const IdeDebuggerAddressRange  *range,
+                                                        GCancellable                   *cancellable,
+                                                        GAsyncReadyCallback             callback,
+                                                        gpointer                        user_data);
+GPtrArray *_ide_debugger_real_disassemble_finish       (IdeDebugger                    *self,
+                                                        GAsyncResult                   *result,
+                                                        GError                        **error);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-register.c b/libide/debugger/ide-debugger-register.c
new file mode 100644
index 0000000..c62c3b5
--- /dev/null
+++ b/libide/debugger/ide-debugger-register.c
@@ -0,0 +1,233 @@
+/* ide-debugger-register.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 "ide-debugger-register"
+
+#include "ide-debugger-register.h"
+
+typedef struct
+{
+  gchar *id;
+  gchar *name;
+  gchar *value;
+} IdeDebuggerRegisterPrivate;
+
+enum {
+  PROP_0,
+  PROP_ID,
+  PROP_NAME,
+  PROP_VALUE,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeDebuggerRegister, ide_debugger_register, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_debugger_register_finalize (GObject *object)
+{
+  IdeDebuggerRegister *self = (IdeDebuggerRegister *)object;
+  IdeDebuggerRegisterPrivate *priv = ide_debugger_register_get_instance_private (self);
+
+  g_clear_pointer (&priv->id, g_free);
+  g_clear_pointer (&priv->name, g_free);
+  g_clear_pointer (&priv->value, g_free);
+
+  G_OBJECT_CLASS (ide_debugger_register_parent_class)->finalize (object);
+}
+
+static void
+ide_debugger_register_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  IdeDebuggerRegister *self = IDE_DEBUGGER_REGISTER (object);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      g_value_set_string (value, ide_debugger_register_get_id (self));
+      break;
+
+    case PROP_NAME:
+      g_value_set_string (value, ide_debugger_register_get_name (self));
+      break;
+
+    case PROP_VALUE:
+      g_value_set_string (value, ide_debugger_register_get_value (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_register_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  IdeDebuggerRegister *self = IDE_DEBUGGER_REGISTER (object);
+  IdeDebuggerRegisterPrivate *priv = ide_debugger_register_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      priv->id = g_value_dup_string (value);
+      break;
+
+    case PROP_NAME:
+      ide_debugger_register_set_name (self, g_value_get_string (value));
+      break;
+
+    case PROP_VALUE:
+      ide_debugger_register_set_value (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_register_class_init (IdeDebuggerRegisterClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_debugger_register_finalize;
+  object_class->get_property = ide_debugger_register_get_property;
+  object_class->set_property = ide_debugger_register_set_property;
+
+  properties [PROP_ID] =
+    g_param_spec_string ("id",
+                         "Id",
+                         "Identifier",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name",
+                         "Name",
+                         "Name",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_VALUE] =
+    g_param_spec_string ("value",
+                         "Value",
+                         "Value",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_debugger_register_init (IdeDebuggerRegister *self)
+{
+}
+
+IdeDebuggerRegister *
+ide_debugger_register_new (const gchar *id)
+{
+  return g_object_new (IDE_TYPE_DEBUGGER_REGISTER,
+                       "id", id,
+                       NULL);
+}
+
+const gchar *
+ide_debugger_register_get_id (IdeDebuggerRegister *self)
+{
+  IdeDebuggerRegisterPrivate *priv = ide_debugger_register_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_REGISTER (self), NULL);
+
+  return priv->id;
+}
+
+const gchar *
+ide_debugger_register_get_name (IdeDebuggerRegister *self)
+{
+  IdeDebuggerRegisterPrivate *priv = ide_debugger_register_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_REGISTER (self), NULL);
+
+  return priv->name;
+}
+
+void
+ide_debugger_register_set_name (IdeDebuggerRegister *self,
+                                const gchar         *name)
+{
+  IdeDebuggerRegisterPrivate *priv = ide_debugger_register_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_REGISTER (self));
+
+  if (g_strcmp0 (priv->name, name) != 0)
+    {
+      g_free (priv->name);
+      priv->name = g_strdup (name);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+    }
+}
+
+const gchar *
+ide_debugger_register_get_value (IdeDebuggerRegister *self)
+{
+  IdeDebuggerRegisterPrivate *priv = ide_debugger_register_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_REGISTER (self), NULL);
+
+  return priv->value;
+}
+
+void
+ide_debugger_register_set_value (IdeDebuggerRegister *self,
+                                 const gchar         *value)
+{
+  IdeDebuggerRegisterPrivate *priv = ide_debugger_register_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_REGISTER (self));
+
+  if (g_strcmp0 (priv->value, value) != 0)
+    {
+      g_free (priv->value);
+      priv->value = g_strdup (value);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VALUE]);
+    }
+}
+
+gint
+ide_debugger_register_compare (IdeDebuggerRegister *a,
+                               IdeDebuggerRegister *b)
+{
+  IdeDebuggerRegisterPrivate *priv_a = ide_debugger_register_get_instance_private (a);
+  IdeDebuggerRegisterPrivate *priv_b = ide_debugger_register_get_instance_private (b);
+
+  if (priv_a->id && priv_b->id)
+    {
+      if (g_ascii_isdigit (*priv_a->id) && g_ascii_isdigit (*priv_b->id))
+        return g_ascii_strtoll (priv_a->id, NULL, 10) -
+               g_ascii_strtoll (priv_b->id, NULL, 10);
+    }
+
+  return g_strcmp0 (priv_a->id, priv_b->id);
+}
diff --git a/libide/debugger/ide-debugger-register.h b/libide/debugger/ide-debugger-register.h
new file mode 100644
index 0000000..6e5d419
--- /dev/null
+++ b/libide/debugger/ide-debugger-register.h
@@ -0,0 +1,54 @@
+/* ide-debugger-register.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/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_REGISTER (ide_debugger_register_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeDebuggerRegister, ide_debugger_register, IDE, DEBUGGER_REGISTER, GObject)
+
+struct _IdeDebuggerRegisterClass
+{
+  GObjectClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+gint                 ide_debugger_register_compare   (IdeDebuggerRegister *a,
+                                                      IdeDebuggerRegister *b);
+IdeDebuggerRegister *ide_debugger_register_new       (const gchar *id);
+const gchar         *ide_debugger_register_get_id    (IdeDebuggerRegister *self);
+const gchar         *ide_debugger_register_get_name  (IdeDebuggerRegister *self);
+void                 ide_debugger_register_set_name  (IdeDebuggerRegister *self,
+                                                      const gchar         *name);
+const gchar         *ide_debugger_register_get_value (IdeDebuggerRegister *self);
+void                 ide_debugger_register_set_value (IdeDebuggerRegister *self,
+                                                      const gchar         *value);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-registers-view.c b/libide/debugger/ide-debugger-registers-view.c
new file mode 100644
index 0000000..8b51886
--- /dev/null
+++ b/libide/debugger/ide-debugger-registers-view.c
@@ -0,0 +1,326 @@
+/* ide-debugger-registers-view.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 "ide-debugger-registers-view"
+
+#include <dazzle.h>
+
+#include "ide-debugger-registers-view.h"
+
+struct _IdeDebuggerRegistersView
+{
+  GtkBin          parent_instance;
+
+  /* Owned references */
+  DzlSignalGroup *debugger_signals;
+
+  /* Template references */
+  GtkTreeView         *tree_view;
+  GtkListStore        *list_store;
+  GtkCellRendererText *id_cell;
+  GtkCellRendererText *name_cell;
+  GtkCellRendererText *value_cell;
+  GtkTreeViewColumn   *id_column;
+  GtkTreeViewColumn   *name_column;
+  GtkTreeViewColumn   *value_column;
+};
+
+enum {
+  PROP_0,
+  PROP_DEBUGGER,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (IdeDebuggerRegistersView, ide_debugger_registers_view, GTK_TYPE_BIN)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_debugger_registers_view_bind (IdeDebuggerRegistersView *self,
+                                  IdeDebugger              *debugger,
+                                  DzlSignalGroup           *signals)
+{
+  g_assert (IDE_IS_DEBUGGER_REGISTERS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+  g_assert (DZL_IS_SIGNAL_GROUP (signals));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->tree_view),
+                            !ide_debugger_get_is_running (debugger));
+}
+
+static void
+ide_debugger_registers_view_unbind (IdeDebuggerRegistersView *self,
+                                    DzlSignalGroup           *signals)
+{
+  g_assert (IDE_IS_DEBUGGER_REGISTERS_VIEW (self));
+  g_assert (DZL_IS_SIGNAL_GROUP (signals));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->tree_view), FALSE);
+}
+
+static void
+ide_debugger_registers_view_running (IdeDebuggerRegistersView *self,
+                                     IdeDebugger              *debugger)
+{
+  g_assert (IDE_IS_DEBUGGER_REGISTERS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->tree_view), FALSE);
+}
+
+static void
+ide_debugger_registers_view_list_registers_cb (GObject      *object,
+                                               GAsyncResult *result,
+                                               gpointer      user_data)
+{
+  IdeDebugger *debugger = (IdeDebugger *)object;
+  g_autoptr(IdeDebuggerRegistersView) self = user_data;
+  g_autoptr(GPtrArray) registers = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_DEBUGGER (debugger));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_DEBUGGER_REGISTERS_VIEW (self));
+
+  gtk_list_store_clear (self->list_store);
+
+  registers = ide_debugger_list_registers_finish (debugger, result, &error);
+
+  if (error != NULL)
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+        g_warning ("%s", error->message);
+      return;
+    }
+
+  if (registers != NULL)
+    {
+      for (guint i = 0; i < registers->len; i++)
+        {
+          IdeDebuggerRegister *reg = g_ptr_array_index (registers, i);
+          GtkTreeIter iter;
+
+          dzl_gtk_list_store_insert_sorted (self->list_store, &iter, reg, 0,
+                                            (GCompareDataFunc)ide_debugger_register_compare,
+                                            NULL);
+          gtk_list_store_set (self->list_store, &iter, 0, reg, -1);
+        }
+    }
+}
+
+static void
+ide_debugger_registers_view_stopped (IdeDebuggerRegistersView *self,
+                                     IdeDebuggerStopReason     stop_reason,
+                                     IdeDebuggerBreakpoint    *breakpoint,
+                                     IdeDebugger              *debugger)
+{
+  g_assert (IDE_IS_DEBUGGER_REGISTERS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_STOP_REASON (stop_reason));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  ide_debugger_list_registers_async (debugger,
+                                     NULL,
+                                     ide_debugger_registers_view_list_registers_cb,
+                                     g_object_ref (self));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->tree_view), TRUE);
+}
+
+static void
+string_property_cell_data_func (GtkCellLayout   *cell_layout,
+                                GtkCellRenderer *cell,
+                                GtkTreeModel    *model,
+                                GtkTreeIter     *iter,
+                                gpointer         user_data)
+{
+  const gchar *property = user_data;
+  g_autoptr(GObject) object = NULL;
+  g_auto(GValue) value = G_VALUE_INIT;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+  g_assert (property != NULL);
+
+  g_value_init (&value, G_TYPE_STRING);
+  gtk_tree_model_get (model, iter, 0, &object, -1);
+  if (object != NULL)
+    g_object_get_property (object, property, &value);
+  g_object_set_property (G_OBJECT (cell), "text", &value);
+}
+
+static void
+ide_debugger_registers_view_destroy (GtkWidget *widget)
+{
+  IdeDebuggerRegistersView *self = (IdeDebuggerRegistersView *)widget;
+
+  g_clear_object (&self->debugger_signals);
+
+  GTK_WIDGET_CLASS (ide_debugger_registers_view_parent_class)->destroy (widget);
+}
+
+static void
+ide_debugger_registers_view_get_property (GObject    *object,
+                                          guint       prop_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  IdeDebuggerRegistersView *self = IDE_DEBUGGER_REGISTERS_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEBUGGER:
+      g_value_set_object (value, ide_debugger_registers_view_get_debugger (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_registers_view_set_property (GObject      *object,
+                                          guint         prop_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  IdeDebuggerRegistersView *self = IDE_DEBUGGER_REGISTERS_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEBUGGER:
+      ide_debugger_registers_view_set_debugger (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_registers_view_class_init (IdeDebuggerRegistersViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = ide_debugger_registers_view_get_property;
+  object_class->set_property = ide_debugger_registers_view_set_property;
+
+  widget_class->destroy = ide_debugger_registers_view_destroy;
+
+  properties [PROP_DEBUGGER] =
+    g_param_spec_object ("debugger",
+                         "Debugger",
+                         "The debugger instance",
+                         IDE_TYPE_DEBUGGER,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-debugger-registers-view.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerRegistersView, id_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerRegistersView, id_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerRegistersView, list_store);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerRegistersView, name_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerRegistersView, name_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerRegistersView, tree_view);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerRegistersView, value_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerRegistersView, value_column);
+
+  g_type_ensure (IDE_TYPE_DEBUGGER_REGISTER);
+}
+
+static void
+ide_debugger_registers_view_init (IdeDebuggerRegistersView *self)
+{
+  self->debugger_signals = dzl_signal_group_new (IDE_TYPE_DEBUGGER);
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  g_signal_connect_swapped (self->debugger_signals,
+                            "bind",
+                            G_CALLBACK (ide_debugger_registers_view_bind),
+                            self);
+
+  g_signal_connect_swapped (self->debugger_signals,
+                            "unbind",
+                            G_CALLBACK (ide_debugger_registers_view_unbind),
+                            self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "running",
+                                    G_CALLBACK (ide_debugger_registers_view_running),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "stopped",
+                                    G_CALLBACK (ide_debugger_registers_view_stopped),
+                                    self);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->id_column),
+                                      GTK_CELL_RENDERER (self->id_cell),
+                                      string_property_cell_data_func, "id", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->name_column),
+                                      GTK_CELL_RENDERER (self->name_cell),
+                                      string_property_cell_data_func, "name", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->value_column),
+                                      GTK_CELL_RENDERER (self->value_cell),
+                                      string_property_cell_data_func, "value", NULL);
+}
+
+GtkWidget *
+ide_debugger_registers_view_new (void)
+{
+  return g_object_new (IDE_TYPE_DEBUGGER_REGISTERS_VIEW, NULL);
+}
+
+/**
+ * ide_debugger_registers_view_get_debugger:
+ * @self: a #IdeDebuggerRegistersView
+ *
+ *
+ *
+ * Returns: (transfer none) (nullable): An #IdeDebugger or %NULL
+ */
+IdeDebugger *
+ide_debugger_registers_view_get_debugger (IdeDebuggerRegistersView *self)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER_REGISTERS_VIEW (self), NULL);
+
+  if (self->debugger_signals != NULL)
+    return dzl_signal_group_get_target (self->debugger_signals);
+
+  return NULL;
+}
+
+void
+ide_debugger_registers_view_set_debugger (IdeDebuggerRegistersView *self,
+                                          IdeDebugger              *debugger)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER_REGISTERS_VIEW (self));
+  g_return_if_fail (!debugger || IDE_IS_DEBUGGER (debugger));
+
+  if (self->debugger_signals != NULL)
+    {
+      dzl_signal_group_set_target (self->debugger_signals, debugger);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEBUGGER]);
+    }
+}
diff --git a/libide/debugger/ide-debugger-registers-view.h b/libide/debugger/ide-debugger-registers-view.h
new file mode 100644
index 0000000..bb38c31
--- /dev/null
+++ b/libide/debugger/ide-debugger-registers-view.h
@@ -0,0 +1,36 @@
+/* ide-debugger-registers-view.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/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "ide-debugger.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_REGISTERS_VIEW (ide_debugger_registers_view_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDebuggerRegistersView, ide_debugger_registers_view, IDE, DEBUGGER_REGISTERS_VIEW, 
GtkBin)
+
+GtkWidget   *ide_debugger_registers_view_new          (void);
+IdeDebugger *ide_debugger_registers_view_get_debugger (IdeDebuggerRegistersView *self);
+void         ide_debugger_registers_view_set_debugger (IdeDebuggerRegistersView *self,
+                                                       IdeDebugger              *debugger);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-thread-group.c b/libide/debugger/ide-debugger-thread-group.c
new file mode 100644
index 0000000..b2bd0ff
--- /dev/null
+++ b/libide/debugger/ide-debugger-thread-group.c
@@ -0,0 +1,229 @@
+/* ide-debugger-thread-group.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 "ide-debugger-thread-group"
+
+#include "ide-debugger-thread-group.h"
+
+typedef struct
+{
+  gchar *id;
+  gchar *exit_code;
+  gchar *pid;
+} IdeDebuggerThreadGroupPrivate;
+
+enum {
+  PROP_0,
+  PROP_ID,
+  PROP_PID,
+  PROP_EXIT_CODE,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeDebuggerThreadGroup, ide_debugger_thread_group, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_debugger_thread_group_finalize (GObject *object)
+{
+  IdeDebuggerThreadGroup *self = (IdeDebuggerThreadGroup *)object;
+  IdeDebuggerThreadGroupPrivate *priv = ide_debugger_thread_group_get_instance_private (self);
+
+  g_clear_pointer (&priv->id, g_free);
+  g_clear_pointer (&priv->exit_code, g_free);
+  g_clear_pointer (&priv->pid, g_free);
+
+  G_OBJECT_CLASS (ide_debugger_thread_group_parent_class)->finalize (object);
+}
+
+static void
+ide_debugger_thread_group_get_property (GObject    *object,
+                                        guint       prop_id,
+                                        GValue     *value,
+                                        GParamSpec *pspec)
+{
+  IdeDebuggerThreadGroup *self = IDE_DEBUGGER_THREAD_GROUP (object);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      g_value_set_string (value, ide_debugger_thread_group_get_id (self));
+      break;
+
+    case PROP_EXIT_CODE:
+      g_value_set_string (value, ide_debugger_thread_group_get_exit_code (self));
+      break;
+
+    case PROP_PID:
+      g_value_set_string (value, ide_debugger_thread_group_get_pid (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_thread_group_set_property (GObject      *object,
+                                        guint         prop_id,
+                                        const GValue *value,
+                                        GParamSpec   *pspec)
+{
+  IdeDebuggerThreadGroup *self = IDE_DEBUGGER_THREAD_GROUP (object);
+  IdeDebuggerThreadGroupPrivate *priv = ide_debugger_thread_group_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      priv->id = g_value_dup_string (value);
+      break;
+
+    case PROP_PID:
+      ide_debugger_thread_group_set_pid (self, g_value_get_string (value));
+      break;
+
+    case PROP_EXIT_CODE:
+      ide_debugger_thread_group_set_exit_code (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_thread_group_class_init (IdeDebuggerThreadGroupClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_debugger_thread_group_finalize;
+  object_class->get_property = ide_debugger_thread_group_get_property;
+  object_class->set_property = ide_debugger_thread_group_set_property;
+
+  properties [PROP_ID] =
+    g_param_spec_string ("id",
+                         "Id",
+                         "The thread group identifier",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_EXIT_CODE] =
+    g_param_spec_string ("exit-code",
+                         "Exit Code",
+                         "The exit code from the process as a string for portability",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_PID] =
+    g_param_spec_string ("pid",
+                         "Pid",
+                         "The pid of the thread group",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_debugger_thread_group_init (IdeDebuggerThreadGroup *self)
+{
+}
+
+IdeDebuggerThreadGroup *
+ide_debugger_thread_group_new (const gchar *id)
+{
+  return g_object_new (IDE_TYPE_DEBUGGER_THREAD_GROUP,
+                       "id", id,
+                       NULL);
+}
+
+const gchar *
+ide_debugger_thread_group_get_pid (IdeDebuggerThreadGroup *self)
+{
+  IdeDebuggerThreadGroupPrivate *priv = ide_debugger_thread_group_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_THREAD_GROUP (self), NULL);
+
+  return priv->pid;
+}
+
+const gchar *
+ide_debugger_thread_group_get_exit_code (IdeDebuggerThreadGroup *self)
+{
+  IdeDebuggerThreadGroupPrivate *priv = ide_debugger_thread_group_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_THREAD_GROUP (self), NULL);
+
+  return priv->exit_code;
+}
+
+void
+ide_debugger_thread_group_set_pid (IdeDebuggerThreadGroup *self,
+                                   const gchar            *pid)
+{
+  IdeDebuggerThreadGroupPrivate *priv = ide_debugger_thread_group_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD_GROUP (self));
+
+  if (g_strcmp0 (priv->pid, pid) != 0)
+    {
+      g_free (priv->pid);
+      priv->pid = g_strdup (pid);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PID]);
+    }
+}
+
+void
+ide_debugger_thread_group_set_exit_code (IdeDebuggerThreadGroup *self,
+                                         const gchar            *exit_code)
+{
+  IdeDebuggerThreadGroupPrivate *priv = ide_debugger_thread_group_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD_GROUP (self));
+
+  if (g_strcmp0 (priv->exit_code, exit_code) != 0)
+    {
+      g_free (priv->exit_code);
+      priv->exit_code = g_strdup (exit_code);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EXIT_CODE]);
+    }
+}
+
+const gchar *
+ide_debugger_thread_group_get_id (IdeDebuggerThreadGroup *self)
+{
+  IdeDebuggerThreadGroupPrivate *priv = ide_debugger_thread_group_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_THREAD_GROUP (self), NULL);
+
+  return priv->id;
+}
+
+gint
+ide_debugger_thread_group_compare (IdeDebuggerThreadGroup *a,
+                                   IdeDebuggerThreadGroup *b)
+{
+  IdeDebuggerThreadGroupPrivate *priv_a = ide_debugger_thread_group_get_instance_private (a);
+  IdeDebuggerThreadGroupPrivate *priv_b = ide_debugger_thread_group_get_instance_private (b);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_THREAD_GROUP (a), 0);
+  g_return_val_if_fail (IDE_IS_DEBUGGER_THREAD_GROUP (b), 0);
+
+  return g_strcmp0 (priv_a->id, priv_b->id);
+}
diff --git a/libide/debugger/ide-debugger-thread-group.h b/libide/debugger/ide-debugger-thread-group.h
new file mode 100644
index 0000000..749dc44
--- /dev/null
+++ b/libide/debugger/ide-debugger-thread-group.h
@@ -0,0 +1,50 @@
+/* ide-debugger-thread-group.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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_THREAD_GROUP (ide_debugger_thread_group_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeDebuggerThreadGroup, ide_debugger_thread_group, IDE, DEBUGGER_THREAD_GROUP, 
GObject)
+
+struct _IdeDebuggerThreadGroupClass
+{
+  GObjectClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+};
+
+gint                    ide_debugger_thread_group_compare       (IdeDebuggerThreadGroup *a,
+                                                                 IdeDebuggerThreadGroup *b);
+IdeDebuggerThreadGroup *ide_debugger_thread_group_new           (const gchar *id);
+const gchar            *ide_debugger_thread_group_get_id        (IdeDebuggerThreadGroup *self);
+const gchar            *ide_debugger_thread_group_get_pid       (IdeDebuggerThreadGroup *self);
+void                    ide_debugger_thread_group_set_pid       (IdeDebuggerThreadGroup *self,
+                                                                 const gchar            *pid);
+const gchar            *ide_debugger_thread_group_get_exit_code (IdeDebuggerThreadGroup *self);
+void                    ide_debugger_thread_group_set_exit_code (IdeDebuggerThreadGroup *self,
+                                                                 const gchar            *exit_code);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-thread.c b/libide/debugger/ide-debugger-thread.c
new file mode 100644
index 0000000..069dd4b
--- /dev/null
+++ b/libide/debugger/ide-debugger-thread.c
@@ -0,0 +1,192 @@
+/* ide-debugger-thread.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 "ide-debugger-thread"
+
+#include "ide-debugger-thread.h"
+
+typedef struct
+{
+  gchar *id;
+  gchar *group;
+} IdeDebuggerThreadPrivate;
+
+enum {
+  PROP_0,
+  PROP_ID,
+  PROP_GROUP,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeDebuggerThread, ide_debugger_thread, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_debugger_thread_finalize (GObject *object)
+{
+  IdeDebuggerThread *self = (IdeDebuggerThread *)object;
+  IdeDebuggerThreadPrivate *priv = ide_debugger_thread_get_instance_private (self);
+
+  g_clear_pointer (&priv->id, g_free);
+  g_clear_pointer (&priv->group, g_free);
+
+  G_OBJECT_CLASS (ide_debugger_thread_parent_class)->finalize (object);
+}
+
+static void
+ide_debugger_thread_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  IdeDebuggerThread *self = IDE_DEBUGGER_THREAD (object);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      g_value_set_string (value, ide_debugger_thread_get_id (self));
+      break;
+
+    case PROP_GROUP:
+      g_value_set_string (value, ide_debugger_thread_get_group (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_thread_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  IdeDebuggerThread *self = IDE_DEBUGGER_THREAD (object);
+  IdeDebuggerThreadPrivate *priv = ide_debugger_thread_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      priv->id = g_value_dup_string (value);
+      break;
+
+    case PROP_GROUP:
+      ide_debugger_thread_set_group (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_thread_class_init (IdeDebuggerThreadClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_debugger_thread_finalize;
+  object_class->get_property = ide_debugger_thread_get_property;
+  object_class->set_property = ide_debugger_thread_set_property;
+
+  properties [PROP_ID] =
+    g_param_spec_string ("id",
+                         "Id",
+                         "The thread identifier",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_GROUP] =
+    g_param_spec_string ("group",
+                         "Group",
+                         "The thread group, if any",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_debugger_thread_init (IdeDebuggerThread *self)
+{
+}
+
+IdeDebuggerThread *
+ide_debugger_thread_new (const gchar *id)
+{
+  return g_object_new (IDE_TYPE_DEBUGGER_THREAD,
+                       "id", id,
+                       NULL);
+}
+
+const gchar *
+ide_debugger_thread_get_id (IdeDebuggerThread *self)
+{
+  IdeDebuggerThreadPrivate *priv = ide_debugger_thread_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_THREAD (self), NULL);
+
+  return priv->id;
+}
+
+const gchar *
+ide_debugger_thread_get_group (IdeDebuggerThread *self)
+{
+  IdeDebuggerThreadPrivate *priv = ide_debugger_thread_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_THREAD (self), NULL);
+
+  return priv->group;
+}
+
+void
+ide_debugger_thread_set_group (IdeDebuggerThread *self,
+                               const gchar       *group)
+{
+  IdeDebuggerThreadPrivate *priv = ide_debugger_thread_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD (self));
+
+  if (g_strcmp0 (priv->group, group) != 0)
+    {
+      g_free (priv->group);
+      priv->group = g_strdup (group);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_GROUP]);
+    }
+}
+
+gint
+ide_debugger_thread_compare (IdeDebuggerThread *a,
+                             IdeDebuggerThread *b)
+{
+  IdeDebuggerThreadPrivate *priv_a = ide_debugger_thread_get_instance_private (a);
+  IdeDebuggerThreadPrivate *priv_b = ide_debugger_thread_get_instance_private (b);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_THREAD (a), 0);
+  g_return_val_if_fail (IDE_IS_DEBUGGER_THREAD (b), 0);
+
+  if (priv_a->id && priv_b->id)
+    {
+      if (g_ascii_isdigit (*priv_a->id) && g_ascii_isdigit (*priv_b->id))
+        return g_ascii_strtoll (priv_a->id, NULL, 10) -
+               g_ascii_strtoll (priv_b->id, NULL, 10);
+    }
+
+  return g_strcmp0 (priv_a->id, priv_b->id);
+}
diff --git a/libide/debugger/ide-debugger-thread.h b/libide/debugger/ide-debugger-thread.h
new file mode 100644
index 0000000..cc6154e
--- /dev/null
+++ b/libide/debugger/ide-debugger-thread.h
@@ -0,0 +1,47 @@
+/* ide-debugger-thread.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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_THREAD (ide_debugger_thread_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeDebuggerThread, ide_debugger_thread, IDE, DEBUGGER_THREAD, GObject)
+
+struct _IdeDebuggerThreadClass
+{
+  GObjectClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+};
+
+gint               ide_debugger_thread_compare   (IdeDebuggerThread *a,
+                                                  IdeDebuggerThread *b);
+IdeDebuggerThread *ide_debugger_thread_new       (const gchar       *id);
+const gchar       *ide_debugger_thread_get_id    (IdeDebuggerThread *self);
+const gchar       *ide_debugger_thread_get_group (IdeDebuggerThread *self);
+void               ide_debugger_thread_set_group (IdeDebuggerThread *self,
+                                                  const gchar       *thread_group);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-threads-view.c b/libide/debugger/ide-debugger-threads-view.c
new file mode 100644
index 0000000..ab6a64c
--- /dev/null
+++ b/libide/debugger/ide-debugger-threads-view.c
@@ -0,0 +1,820 @@
+/* ide-debugger-threads-view.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 "ide-debugger-threads-view"
+
+#include <dazzle.h>
+#include <glib/gi18n.h>
+
+#include "ide-debugger-threads-view.h"
+
+struct _IdeDebuggerThreadsView
+{
+  GtkBin               parent_instance;
+
+  /* Owned references */
+  DzlSignalGroup      *debugger_signals;
+
+  /* Template References */
+  GtkTreeView         *frames_tree_view;
+  GtkTreeView         *thread_groups_tree_view;
+  GtkTreeView         *threads_tree_view;
+  GtkListStore        *frames_store;
+  GtkListStore        *thread_groups_store;
+  GtkListStore        *threads_store;
+  GtkTreeViewColumn   *args_column;
+  GtkTreeViewColumn   *binary_column;
+  GtkTreeViewColumn   *depth_column;
+  GtkTreeViewColumn   *group_column;
+  GtkTreeViewColumn   *location_column;
+  GtkTreeViewColumn   *thread_column;
+  GtkTreeViewColumn   *function_column;
+  GtkCellRendererText *args_cell;
+  GtkCellRendererText *binary_cell;
+  GtkCellRendererText *depth_cell;
+  GtkCellRendererText *group_cell;
+  GtkCellRendererText *location_cell;
+  GtkCellRendererText *thread_cell;
+  GtkCellRendererText *function_cell;
+};
+
+enum {
+  PROP_0,
+  PROP_DEBUGGER,
+  N_PROPS
+};
+
+enum {
+  FRAME_ACTIVATED,
+  N_SIGNALS
+};
+
+G_DEFINE_TYPE (IdeDebuggerThreadsView, ide_debugger_threads_view, GTK_TYPE_BIN)
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static IdeDebuggerThread *
+ide_debugger_threads_view_get_current_thread (IdeDebuggerThreadsView *self)
+{
+  g_autoptr(IdeDebuggerThread) thread = NULL;
+  GtkTreeSelection *selection;
+  GtkTreeModel *model = NULL;
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+
+  selection = gtk_tree_view_get_selection (self->threads_tree_view);
+  if (gtk_tree_selection_get_selected (selection, &model, &iter))
+    gtk_tree_model_get (model, &iter, 0, &thread, -1);
+
+  return g_steal_pointer (&thread);
+}
+
+static void
+ide_debugger_threads_view_running (IdeDebuggerThreadsView *self,
+                                   IdeDebugger            *debugger)
+{
+  GtkTreeSelection *selection;
+
+  g_assert (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  gtk_list_store_clear (self->frames_store);
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->frames_tree_view), FALSE);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->thread_groups_tree_view), FALSE);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->threads_tree_view), FALSE);
+
+  selection = gtk_tree_view_get_selection (self->threads_tree_view);
+  gtk_tree_selection_unselect_all (selection);
+}
+
+static void
+ide_debugger_threads_view_stopped (IdeDebuggerThreadsView *self,
+                                   IdeDebuggerStopReason   stop_reason,
+                                   IdeDebuggerBreakpoint  *breakpoint,
+                                   IdeDebugger            *debugger)
+{
+  IdeDebuggerThread *selected;
+  GtkTreeSelection *selection;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_STOP_REASON (stop_reason));
+  g_assert (!breakpoint || IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->frames_tree_view), TRUE);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->thread_groups_tree_view), TRUE);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->threads_tree_view), TRUE);
+
+  selected = ide_debugger_get_selected_thread (debugger);
+
+  if (selected != NULL)
+    {
+      model = GTK_TREE_MODEL (self->threads_store);
+
+      if (gtk_tree_model_get_iter_first (model, &iter))
+        {
+          do
+            {
+              g_autoptr(IdeDebuggerThread) thread = NULL;
+
+              gtk_tree_model_get (model, &iter, 0, &thread, -1);
+
+              if (ide_debugger_thread_compare (thread, selected) == 0)
+                {
+                  GtkTreePath *path;
+
+                  selection = gtk_tree_view_get_selection (self->threads_tree_view);
+                  gtk_tree_selection_select_iter (selection, &iter);
+
+                  path = gtk_tree_model_get_path (model, &iter);
+                  gtk_tree_view_row_activated (self->threads_tree_view, path, self->thread_column);
+                  gtk_tree_path_free (path);
+
+                  break;
+                }
+            }
+          while (gtk_tree_model_iter_next (model, &iter));
+        }
+    }
+}
+
+static void
+ide_debugger_threads_view_thread_group_added (IdeDebuggerThreadsView *self,
+                                              IdeDebuggerThreadGroup *group,
+                                              IdeDebugger            *debugger)
+{
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_THREAD_GROUP (group));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  dzl_gtk_list_store_insert_sorted (self->thread_groups_store,
+                                    &iter, group, 0,
+                                    (GCompareDataFunc)ide_debugger_thread_group_compare,
+                                    NULL);
+  gtk_list_store_set (self->thread_groups_store, &iter, 0, group, -1);
+}
+
+static void
+ide_debugger_threads_view_thread_group_removed (IdeDebuggerThreadsView *self,
+                                                IdeDebuggerThreadGroup *group,
+                                                IdeDebugger            *debugger)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_THREAD_GROUP (group));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  model = GTK_TREE_MODEL (self->thread_groups_store);
+
+  if (gtk_tree_model_get_iter_first (model, &iter))
+    {
+      do
+        {
+          g_autoptr(IdeDebuggerThreadGroup) row = NULL;
+
+          gtk_tree_model_get (model, &iter, 0, &row, -1);
+
+          if (ide_debugger_thread_group_compare (row, group) == 0)
+            {
+              gtk_list_store_remove (self->thread_groups_store, &iter);
+              break;
+            }
+        }
+      while (gtk_tree_model_iter_next (model, &iter));
+    }
+}
+
+static void
+ide_debugger_threads_view_thread_added (IdeDebuggerThreadsView *self,
+                                        IdeDebuggerThread      *thread,
+                                        IdeDebugger            *debugger)
+{
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_THREAD (thread));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  dzl_gtk_list_store_insert_sorted (self->threads_store,
+                                    &iter, thread, 0,
+                                    (GCompareDataFunc)ide_debugger_thread_compare,
+                                    NULL);
+  gtk_list_store_set (self->threads_store, &iter, 0, thread, -1);
+}
+
+static void
+ide_debugger_threads_view_thread_removed (IdeDebuggerThreadsView *self,
+                                          IdeDebuggerThread      *thread,
+                                          IdeDebugger            *debugger)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER_THREAD (thread));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+
+  model = GTK_TREE_MODEL (self->threads_store);
+
+  if (gtk_tree_model_get_iter_first (model, &iter))
+    {
+      do
+        {
+          g_autoptr(IdeDebuggerThread) row = NULL;
+
+          gtk_tree_model_get (model, &iter, 0, &row, -1);
+
+          if (ide_debugger_thread_compare (row, thread) == 0)
+            {
+              gtk_list_store_remove (self->threads_store, &iter);
+              break;
+            }
+        }
+      while (gtk_tree_model_iter_next (model, &iter));
+    }
+}
+
+static void
+ide_debugger_threads_view_list_frames_cb (GObject      *object,
+                                          GAsyncResult *result,
+                                          gpointer      user_data)
+{
+  IdeDebugger *debugger = (IdeDebugger *)object;
+  g_autoptr(IdeDebuggerThreadsView) self = user_data;
+  g_autoptr(GPtrArray) frames = NULL;
+  g_autoptr(GError) error = NULL;
+  GtkTreeSelection *selection;
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER (debugger));
+  g_assert (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  frames = ide_debugger_list_frames_finish (debugger, result, &error);
+
+  if (frames == NULL)
+    {
+      g_warning ("%s", error->message);
+      return;
+    }
+
+  gtk_list_store_clear (self->frames_store);
+
+  for (guint i = 0; i < frames->len; i++)
+    {
+      IdeDebuggerFrame *frame = g_ptr_array_index (frames, i);
+
+      g_assert (IDE_IS_DEBUGGER_FRAME (frame));
+
+      gtk_list_store_append (self->frames_store, &iter);
+      gtk_list_store_set (self->frames_store, &iter, 0, frame, -1);
+    }
+
+  if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->frames_store), &iter))
+    {
+      GtkTreePath *path;
+
+      selection = gtk_tree_view_get_selection (self->frames_tree_view);
+      gtk_tree_selection_select_iter (selection, &iter);
+
+      path = gtk_tree_model_get_path (GTK_TREE_MODEL (self->frames_store), &iter);
+      gtk_tree_view_row_activated (self->frames_tree_view, path, self->depth_column);
+      gtk_tree_path_free (path);
+    }
+}
+
+static void
+ide_debugger_threads_view_bind (IdeDebuggerThreadsView *self,
+                                IdeDebugger            *debugger,
+                                DzlSignalGroup         *debugger_signals)
+{
+  GListModel *thread_groups;
+  GListModel *threads;
+  guint n_items;
+
+  g_assert (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+  g_assert (IDE_IS_DEBUGGER (debugger));
+  g_assert (DZL_IS_SIGNAL_GROUP (debugger_signals));
+
+  /* Add any thread groups already loaded by the debugger */
+
+  thread_groups = ide_debugger_get_thread_groups (debugger);
+  n_items = g_list_model_get_n_items (thread_groups);
+
+  for (guint i = 0; i < n_items; i++)
+    {
+      g_autoptr(IdeDebuggerThreadGroup) group = NULL;
+
+      group = g_list_model_get_item (thread_groups, i);
+      ide_debugger_threads_view_thread_group_added (self, group, debugger);
+    }
+
+  /* Add any threads already loaded by the debugger */
+
+  threads = ide_debugger_get_threads (debugger);
+  n_items = g_list_model_get_n_items (threads);
+
+  for (guint i = 0; i < n_items; i++)
+    {
+      g_autoptr(IdeDebuggerThread) thread = NULL;
+
+      thread = g_list_model_get_item (threads, i);
+      ide_debugger_threads_view_thread_added (self, thread, debugger);
+    }
+}
+
+static void
+ide_debugger_threads_view_unbind (IdeDebuggerThreadsView *self,
+                                  DzlSignalGroup         *debugger_signals)
+{
+  g_assert (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+  g_assert (DZL_IS_SIGNAL_GROUP (debugger_signals));
+
+  gtk_list_store_clear (self->thread_groups_store);
+}
+
+static void
+argv_property_cell_data_func (GtkCellLayout   *cell_layout,
+                              GtkCellRenderer *cell,
+                              GtkTreeModel    *model,
+                              GtkTreeIter     *iter,
+                              gpointer         user_data)
+{
+  const gchar *property = user_data;
+  g_autoptr(GObject) object = NULL;
+  g_auto(GStrv) strv = NULL;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+  g_assert (property != NULL);
+
+  gtk_tree_model_get (model, iter, 0, &object, -1);
+
+  if (object != NULL)
+    {
+      g_object_get (object, property, &strv, NULL);
+
+      if (strv != NULL)
+        {
+          g_autoptr(GString) str = g_string_new (NULL);
+
+          g_string_append_c (str, '(');
+          for (guint i = 0; strv[i]; i++)
+            {
+              g_string_append (str, strv[i]);
+              if (strv[i+1])
+                g_string_append (str, ", ");
+            }
+          g_string_append_c (str, ')');
+
+          g_object_set (cell, "text", str->str, NULL);
+
+          return;
+        }
+    }
+
+  g_object_set (cell, "text", "", NULL);
+}
+
+static void
+location_property_cell_data_func (GtkCellLayout   *cell_layout,
+                                  GtkCellRenderer *cell,
+                                  GtkTreeModel    *model,
+                                  GtkTreeIter     *iter,
+                                  gpointer         user_data)
+{
+  g_autoptr(IdeDebuggerFrame) frame = NULL;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+
+  gtk_tree_model_get (model, iter, 0, &frame, -1);
+
+  if (frame != NULL)
+    {
+      const gchar *file = ide_debugger_frame_get_file (frame);
+      guint line = ide_debugger_frame_get_line (frame);
+
+      if (file != NULL)
+        {
+          if (line != 0)
+            {
+              g_autofree gchar *text = NULL;
+
+              text = g_strdup_printf ("%s<span fgalpha='32767'>:%u</span>", file, line);
+              g_object_set (cell, "markup", text, NULL);
+            }
+          else
+            {
+              g_object_set (cell, "text", file, NULL);
+            }
+
+          return;
+        }
+    }
+
+  g_object_set (cell, "text", NULL, NULL);
+}
+
+static void
+binary_property_cell_data_func (GtkCellLayout   *cell_layout,
+                                GtkCellRenderer *cell,
+                                GtkTreeModel    *model,
+                                GtkTreeIter     *iter,
+                                gpointer         user_data)
+{
+  IdeDebuggerThreadsView *self = user_data;
+  IdeDebugger *debugger;
+  g_autoptr(IdeDebuggerFrame) frame = NULL;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+
+  debugger = dzl_signal_group_get_target (self->debugger_signals);
+  if (debugger == NULL)
+    return;
+
+  gtk_tree_model_get (model, iter, 0, &frame, -1);
+
+  if (frame != NULL)
+    {
+      IdeDebuggerAddress address;
+      const gchar *name;
+
+      address = ide_debugger_frame_get_address (frame);
+      name = ide_debugger_locate_binary_at_address (debugger, address);
+      g_object_set (cell, "text", name, NULL);
+      return;
+    }
+
+  g_object_set (cell, "text", NULL, NULL);
+}
+
+static void
+string_property_cell_data_func (GtkCellLayout   *cell_layout,
+                                GtkCellRenderer *cell,
+                                GtkTreeModel    *model,
+                                GtkTreeIter     *iter,
+                                gpointer         user_data)
+{
+  const gchar *property = user_data;
+  g_autoptr(GObject) object = NULL;
+  g_auto(GValue) value = G_VALUE_INIT;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+  g_assert (property != NULL);
+
+  g_value_init (&value, G_TYPE_STRING);
+  gtk_tree_model_get (model, iter, 0, &object, -1);
+
+  if (object != NULL)
+    g_object_get_property (object, property, &value);
+
+  g_object_set_property (G_OBJECT (cell), "text", &value);
+}
+
+static void
+int_property_cell_data_func (GtkCellLayout   *cell_layout,
+                             GtkCellRenderer *cell,
+                             GtkTreeModel    *model,
+                             GtkTreeIter     *iter,
+                             gpointer         user_data)
+{
+  const gchar *property = user_data;
+  g_autoptr(GObject) object = NULL;
+  g_auto(GValue) value = G_VALUE_INIT;
+  g_autofree gchar *str = NULL;
+
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (cell_layout));
+  g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+  g_assert (GTK_IS_TREE_MODEL (model));
+  g_assert (iter != NULL);
+  g_assert (property != NULL);
+
+  g_value_init (&value, G_TYPE_INT64);
+  gtk_tree_model_get (model, iter, 0, &object, -1);
+
+  if (object != NULL)
+    g_object_get_property (object, property, &value);
+
+  str = g_strdup_printf ("%"G_GINT64_FORMAT, g_value_get_int64 (&value));
+  g_object_set (cell, "text", str, NULL);
+}
+
+static void
+ide_debugger_threads_view_threads_row_activated (IdeDebuggerThreadsView *self,
+                                                 GtkTreePath            *path,
+                                                 GtkTreeViewColumn      *column,
+                                                 GtkTreeView            *tree_view)
+{
+  IdeDebugger *debugger;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+  g_assert (path != NULL);
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (column));
+  g_assert (GTK_IS_TREE_VIEW (tree_view));
+
+  model = gtk_tree_view_get_model (tree_view);
+  debugger = dzl_signal_group_get_target (self->debugger_signals);
+
+  if (debugger == NULL)
+    return;
+
+  if (gtk_tree_model_get_iter (model, &iter, path))
+    {
+      g_autoptr(IdeDebuggerThread) thread = NULL;
+
+      gtk_tree_model_get (model, &iter, 0, &thread, -1);
+      g_assert (!thread || IDE_IS_DEBUGGER_THREAD (thread));
+
+      if (thread != NULL)
+        {
+          ide_debugger_list_frames_async (debugger,
+                                          thread,
+                                          NULL,
+                                          ide_debugger_threads_view_list_frames_cb,
+                                          g_object_ref (self));
+        }
+    }
+}
+
+static void
+ide_debugger_threads_view_frames_row_activated (IdeDebuggerThreadsView *self,
+                                                GtkTreePath            *path,
+                                                GtkTreeViewColumn      *column,
+                                                GtkTreeView            *tree_view)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  g_assert (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+  g_assert (path != NULL);
+  g_assert (GTK_IS_TREE_VIEW_COLUMN (column));
+  g_assert (GTK_IS_TREE_VIEW (tree_view));
+
+  model = gtk_tree_view_get_model (tree_view);
+
+  if (gtk_tree_model_get_iter (model, &iter, path))
+    {
+      g_autoptr(IdeDebuggerFrame) frame = NULL;
+
+      gtk_tree_model_get (model, &iter, 0, &frame, -1);
+
+      if (frame != NULL)
+        {
+          g_autoptr(IdeDebuggerThread) thread = NULL;
+
+          thread = ide_debugger_threads_view_get_current_thread (self);
+          if (thread != NULL && frame != NULL)
+            g_signal_emit (self, signals [FRAME_ACTIVATED], 0, thread, frame);
+        }
+    }
+}
+
+static void
+ide_debugger_threads_view_dispose (GObject *object)
+{
+  IdeDebuggerThreadsView *self = (IdeDebuggerThreadsView *)object;
+
+  g_clear_object (&self->debugger_signals);
+
+  G_OBJECT_CLASS (ide_debugger_threads_view_parent_class)->dispose (object);
+}
+
+static void
+ide_debugger_threads_view_get_property (GObject    *object,
+                                        guint       prop_id,
+                                        GValue     *value,
+                                        GParamSpec *pspec)
+{
+  IdeDebuggerThreadsView *self = IDE_DEBUGGER_THREADS_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEBUGGER:
+      g_value_set_object (value, ide_debugger_threads_view_get_debugger (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_threads_view_set_property (GObject      *object,
+                                        guint         prop_id,
+                                        const GValue *value,
+                                        GParamSpec   *pspec)
+{
+  IdeDebuggerThreadsView *self = IDE_DEBUGGER_THREADS_VIEW (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEBUGGER:
+      ide_debugger_threads_view_set_debugger (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_threads_view_class_init (IdeDebuggerThreadsViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = ide_debugger_threads_view_dispose;
+  object_class->get_property = ide_debugger_threads_view_get_property;
+  object_class->set_property = ide_debugger_threads_view_set_property;
+
+  properties [PROP_DEBUGGER] =
+    g_param_spec_object ("debugger",
+                         "Debugger",
+                         "Debugger",
+                         IDE_TYPE_DEBUGGER,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  signals [FRAME_ACTIVATED] =
+    g_signal_new ("frame-activated",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE, 2, IDE_TYPE_DEBUGGER_THREAD, IDE_TYPE_DEBUGGER_FRAME);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-debugger-threads-view.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, args_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, args_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, binary_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, binary_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, depth_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, depth_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, frames_store);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, frames_tree_view);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, function_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, function_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, group_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, group_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, location_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, location_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, thread_cell);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, thread_column);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, thread_groups_store);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, thread_groups_tree_view);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, threads_store);
+  gtk_widget_class_bind_template_child (widget_class, IdeDebuggerThreadsView, threads_tree_view);
+
+  g_type_ensure (IDE_TYPE_DEBUGGER_FRAME);
+  g_type_ensure (IDE_TYPE_DEBUGGER_THREAD);
+  g_type_ensure (IDE_TYPE_DEBUGGER_THREAD_GROUP);
+}
+
+static void
+ide_debugger_threads_view_init (IdeDebuggerThreadsView *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->debugger_signals = dzl_signal_group_new (IDE_TYPE_DEBUGGER);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "running",
+                                    G_CALLBACK (ide_debugger_threads_view_running),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "stopped",
+                                    G_CALLBACK (ide_debugger_threads_view_stopped),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "thread-group-added",
+                                    G_CALLBACK (ide_debugger_threads_view_thread_group_added),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "thread-group-removed",
+                                    G_CALLBACK (ide_debugger_threads_view_thread_group_removed),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "thread-added",
+                                    G_CALLBACK (ide_debugger_threads_view_thread_added),
+                                    self);
+
+  dzl_signal_group_connect_swapped (self->debugger_signals,
+                                    "thread-removed",
+                                    G_CALLBACK (ide_debugger_threads_view_thread_removed),
+                                    self);
+
+  g_signal_connect_swapped (self->debugger_signals,
+                            "bind",
+                            G_CALLBACK (ide_debugger_threads_view_bind),
+                            self);
+
+  g_signal_connect_swapped (self->debugger_signals,
+                            "unbind",
+                            G_CALLBACK (ide_debugger_threads_view_unbind),
+                            self);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->group_column),
+                                      GTK_CELL_RENDERER (self->group_cell),
+                                      string_property_cell_data_func, "id", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->thread_column),
+                                      GTK_CELL_RENDERER (self->thread_cell),
+                                      string_property_cell_data_func, "id", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->depth_column),
+                                      GTK_CELL_RENDERER (self->depth_cell),
+                                      int_property_cell_data_func, "depth", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->function_column),
+                                      GTK_CELL_RENDERER (self->function_cell),
+                                      string_property_cell_data_func, "function", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->args_column),
+                                      GTK_CELL_RENDERER (self->args_cell),
+                                      argv_property_cell_data_func, "args", NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->location_column),
+                                      GTK_CELL_RENDERER (self->location_cell),
+                                      location_property_cell_data_func, NULL, NULL);
+
+  gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (self->binary_column),
+                                      GTK_CELL_RENDERER (self->binary_cell),
+                                      binary_property_cell_data_func, self, NULL);
+
+  g_signal_connect_swapped (self->threads_tree_view,
+                            "row-activated",
+                            G_CALLBACK (ide_debugger_threads_view_threads_row_activated),
+                            self);
+
+  g_signal_connect_swapped (self->frames_tree_view,
+                            "row-activated",
+                            G_CALLBACK (ide_debugger_threads_view_frames_row_activated),
+                            self);
+}
+
+/**
+ * ide_debugger_threads_view_get_debugger:
+ * @self: a #IdeDebuggerThreadsView
+ *
+ * Gets the debugger that is being observed.
+ *
+ * Returns: (transfer none) (nullable): An #IdeDebugger or %NULL
+ */
+IdeDebugger *
+ide_debugger_threads_view_get_debugger (IdeDebuggerThreadsView *self)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER_THREADS_VIEW (self), NULL);
+
+  return dzl_signal_group_get_target (self->debugger_signals);
+}
+
+void
+ide_debugger_threads_view_set_debugger (IdeDebuggerThreadsView *self,
+                                        IdeDebugger            *debugger)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER_THREADS_VIEW (self));
+  g_return_if_fail (IDE_IS_DEBUGGER (debugger));
+
+  dzl_signal_group_set_target (self->debugger_signals, debugger);
+}
diff --git a/libide/debugger/ide-debugger-threads-view.h b/libide/debugger/ide-debugger-threads-view.h
new file mode 100644
index 0000000..668392a
--- /dev/null
+++ b/libide/debugger/ide-debugger-threads-view.h
@@ -0,0 +1,35 @@
+/* ide-debugger-threads-view.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/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "ide-debugger.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_THREADS_VIEW (ide_debugger_threads_view_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDebuggerThreadsView, ide_debugger_threads_view, IDE, DEBUGGER_THREADS_VIEW, GtkBin)
+
+IdeDebugger *ide_debugger_threads_view_get_debugger (IdeDebuggerThreadsView *self);
+void         ide_debugger_threads_view_set_debugger (IdeDebuggerThreadsView *self,
+                                                     IdeDebugger            *debugger);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-types.c b/libide/debugger/ide-debugger-types.c
new file mode 100644
index 0000000..80cd7c5
--- /dev/null
+++ b/libide/debugger/ide-debugger-types.c
@@ -0,0 +1,189 @@
+/* ide-debugger-types.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 "ide-debugger-types"
+
+#include "ide-debugger-types.h"
+
+GType
+ide_debugger_stream_get_type (void)
+{
+  static GType type_id;
+
+  if (g_once_init_enter (&type_id))
+    {
+      GType _type_id;
+      static const GEnumValue values[] = {
+        { IDE_DEBUGGER_CONSOLE, "IDE_DEBUGGER_CONSOLE", "console" },
+        { IDE_DEBUGGER_EVENT_LOG, "IDE_DEBUGGER_EVENT_LOG", "log" },
+        { IDE_DEBUGGER_TARGET, "IDE_DEBUGGER_TARGET", "target" },
+        { 0 }
+      };
+
+      _type_id = g_enum_register_static ("IdeDebuggerStream", values);
+      g_once_init_leave (&type_id, _type_id);
+    }
+
+  return type_id;
+}
+
+GType
+ide_debugger_movement_get_type (void)
+{
+  static GType type_id;
+
+  if (g_once_init_enter (&type_id))
+    {
+      GType _type_id;
+      static const GEnumValue values[] = {
+        { IDE_DEBUGGER_MOVEMENT_START, "IDE_DEBUGGER_MOVEMENT_START", "start" },
+        { IDE_DEBUGGER_MOVEMENT_CONTINUE, "IDE_DEBUGGER_MOVEMENT_CONTINUE", "continue" },
+        { IDE_DEBUGGER_MOVEMENT_STEP_IN, "IDE_DEBUGGER_MOVEMENT_STEP_IN", "step-in" },
+        { IDE_DEBUGGER_MOVEMENT_STEP_OVER, "IDE_DEBUGGER_MOVEMENT_STEP_OUT", "step-out" },
+        { 0 }
+      };
+
+      _type_id = g_enum_register_static ("IdeDebuggerMovement", values);
+      g_once_init_leave (&type_id, _type_id);
+    }
+
+  return type_id;
+}
+
+GType
+ide_debugger_stop_reason_get_type (void)
+{
+  static GType type_id;
+
+  if (g_once_init_enter (&type_id))
+    {
+      GType _type_id;
+      static const GEnumValue values[] = {
+        { IDE_DEBUGGER_STOP_BREAKPOINT_HIT, "IDE_DEBUGGER_STOP_BREAKPOINT_HIT", "breakpoint-hit" },
+        { IDE_DEBUGGER_STOP_CATCH, "IDE_DEBUGGER_STOP_CATCH", "catch" },
+        { IDE_DEBUGGER_STOP_EXITED, "IDE_DEBUGGER_STOP_EXITED", "stop-exited" },
+        { IDE_DEBUGGER_STOP_EXITED_NORMALLY, "IDE_DEBUGGER_STOP_EXITED_NORMALLY", "exited-normally" },
+        { IDE_DEBUGGER_STOP_EXITED_SIGNALED, "IDE_DEBUGGER_STOP_EXITED_SIGNALED", "exited-signaled" },
+        { IDE_DEBUGGER_STOP_FUNCTION_FINISHED, "IDE_DEBUGGER_STOP_FUNCTION_FINISHED", "function-finished" },
+        { IDE_DEBUGGER_STOP_LOCATION_REACHED, "IDE_DEBUGGER_STOP_LOCATION_REACHED", "location-reached" },
+        { IDE_DEBUGGER_STOP_SIGNAL_RECEIVED, "IDE_DEBUGGER_STOP_SIGNAL_RECEIVED", "signal-received" },
+        { IDE_DEBUGGER_STOP_UNKNOWN, "IDE_DEBUGGER_STOP_UNKNOWN", "unknown" },
+        { 0 }
+      };
+
+      _type_id = g_enum_register_static ("IdeDebuggerStopReason", values);
+      g_once_init_leave (&type_id, _type_id);
+    }
+
+  return type_id;
+}
+
+GType
+ide_debugger_break_mode_get_type (void)
+{
+  static GType type_id;
+
+  if (g_once_init_enter (&type_id))
+    {
+      GType _type_id;
+      static const GEnumValue values[] = {
+        { IDE_DEBUGGER_BREAK_BREAKPOINT, "IDE_DEBUGGER_BREAK_BREAKPOINT", "breakpoint" },
+        { IDE_DEBUGGER_BREAK_COUNTPOINT, "IDE_DEBUGGER_BREAK_COUNTPOINT", "countpoint" },
+        { IDE_DEBUGGER_BREAK_WATCHPOINT, "IDE_DEBUGGER_BREAK_WATCHPOINT", "watchpoint" },
+        { 0 }
+      };
+
+      _type_id = g_enum_register_static ("IdeDebuggerBreakMode", values);
+      g_once_init_leave (&type_id, _type_id);
+    }
+
+  return type_id;
+}
+
+GType
+ide_debugger_disposition_get_type (void)
+{
+  static GType type_id;
+
+  if (g_once_init_enter (&type_id))
+    {
+      GType _type_id;
+      static const GEnumValue values[] = {
+        { IDE_DEBUGGER_DISPOSITION_KEEP, "IDE_DEBUGGER_DISPOSITION_KEEP", "keep" },
+        { IDE_DEBUGGER_DISPOSITION_DISABLE, "IDE_DEBUGGER_DISPOSITION_DISABLE", "disable" },
+        { IDE_DEBUGGER_DISPOSITION_DELETE_NEXT_HIT, "IDE_DEBUGGER_DISPOSITION_DELETE_NEXT_HIT", 
"delete-next-hit" },
+        { IDE_DEBUGGER_DISPOSITION_DELETE_NEXT_STOP, "IDE_DEBUGGER_DISPOSITION_DELETE_NEXT_STOP", 
"delete-next-stop" },
+        { 0 }
+      };
+
+      _type_id = g_enum_register_static ("IdeDebuggerDisposition", values);
+      g_once_init_leave (&type_id, _type_id);
+    }
+
+  return type_id;
+}
+
+GType
+ide_debugger_breakpoint_change_get_type (void)
+{
+  static GType type_id;
+
+  if (g_once_init_enter (&type_id))
+    {
+      GType _type_id;
+      static const GEnumValue values[] = {
+        { IDE_DEBUGGER_BREAKPOINT_CHANGE_ENABLED, "IDE_DEBUGGER_BREAKPOINT_CHANGE_ENABLED", "enabled" },
+        { 0 }
+      };
+
+      _type_id = g_enum_register_static ("IdeDebuggerBreakpointChange", values);
+      g_once_init_leave (&type_id, _type_id);
+    }
+
+  return type_id;
+}
+
+G_DEFINE_BOXED_TYPE (IdeDebuggerAddressRange,
+                     ide_debugger_address_range,
+                     ide_debugger_address_range_copy,
+                     ide_debugger_address_range_free)
+
+IdeDebuggerAddressRange *
+ide_debugger_address_range_copy (const IdeDebuggerAddressRange *range)
+{
+  return g_slice_dup (IdeDebuggerAddressRange, range);
+}
+
+void
+ide_debugger_address_range_free (IdeDebuggerAddressRange *range)
+{
+  if (range != NULL)
+    g_slice_free (IdeDebuggerAddressRange, range);
+}
+
+IdeDebuggerAddress
+ide_debugger_address_parse (const gchar *string)
+{
+  if (string == NULL)
+    return 0;
+
+  if (g_str_has_prefix (string, "0x"))
+    string += 2;
+
+  return g_ascii_strtoull (string, NULL, 16);
+}
diff --git a/libide/debugger/ide-debugger-types.h b/libide/debugger/ide-debugger-types.h
new file mode 100644
index 0000000..a2a1353
--- /dev/null
+++ b/libide/debugger/ide-debugger-types.h
@@ -0,0 +1,196 @@
+/* ide-debugger-types.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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+/**
+ * IdeDebuggerStream:
+ * @IDE_DEBUGGER_TARGET: Logging from the inferior process
+ * @IDE_DEBUGGER_CONSOLE: Logging from the debugger console
+ * @IDE_DEBUGGER_EVENT_LOG: Internal event log from the debugger that can be
+ *   used to troubleshoot the debugger.
+ *
+ * The type of stream for the log message.
+ *
+ * Since: 3.26
+ */
+typedef enum
+{
+  IDE_DEBUGGER_TARGET,
+  IDE_DEBUGGER_CONSOLE,
+  IDE_DEBUGGER_EVENT_LOG,
+} IdeDebuggerStream;
+
+#define IDE_TYPE_DEBUGGER_STREAM  (ide_debugger_stream_get_type())
+#define IDE_IS_DEBUGGER_STREAM(s) (((gint)s >= 0) && ((gint)s <= IDE_DEBUGGER_EVENT_LOG))
+
+/**
+ * IdeDebuggerMovement:
+ * @IDE_DEBUGGER_MOVEMENT_START: Start or restart the application
+ * @IDE_DEBUGGER_MOVEMENT_CONTINUE: Continue until a breakpoint is reached
+ * @IDE_DEBUGGER_MOVEMENT_STEP_IN: Execute the next line of code, stepping into
+ *   any function.
+ * @IDE_DEBUGGER_MOVEMENT_STEP_OVER: Execute the next line of code, stepping over
+ *   any function.
+ *
+ * Describes the style of movement that should be performed by the debugger.
+ *
+ * Since: 3.26
+ */
+typedef enum
+{
+  IDE_DEBUGGER_MOVEMENT_START,
+  IDE_DEBUGGER_MOVEMENT_CONTINUE,
+  IDE_DEBUGGER_MOVEMENT_STEP_IN,
+  IDE_DEBUGGER_MOVEMENT_STEP_OVER,
+} IdeDebuggerMovement;
+
+#define IDE_TYPE_DEBUGGER_MOVEMENT  (ide_debugger_movement_get_type())
+#define IDE_IS_DEBUGGER_MOVEMENT(m) (((gint)m >= 0) && ((gint)m) <= IDE_DEBUGGER_MOVEMENT_STEP_OVER)
+
+/**
+ * IdeDebuggerStopReason:
+ * @IDE_DEBUGGER_STOP_BREAKPOINT: The debugger stopped because of a breakpoing
+ * @IDE_DEBUGGER_STOP_EXITED_NORMALLY: The debugger stopped because the process exited
+ *    in a graceful fashion.
+ * @IDE_DEBUGGER_STOP_SIGNALED: The debugger stopped because the process
+ *    received a death signal.
+ *
+ * Represents the reason a process has stopped executing in the debugger.
+ */
+typedef enum
+{
+  IDE_DEBUGGER_STOP_BREAKPOINT_HIT,
+  IDE_DEBUGGER_STOP_EXITED,
+  IDE_DEBUGGER_STOP_EXITED_NORMALLY,
+  IDE_DEBUGGER_STOP_EXITED_SIGNALED,
+  IDE_DEBUGGER_STOP_FUNCTION_FINISHED,
+  IDE_DEBUGGER_STOP_LOCATION_REACHED,
+  IDE_DEBUGGER_STOP_SIGNAL_RECEIVED,
+  /* I think this can be used for a variety of catch positions in gdb,
+   * and as a generic fallback for "this stopped, but not for the reason
+   * of a particular breakpoint". Alternatively, a backend could insert
+   * a transient breakpoint, stop on the breakpoint, and then remove it
+   * after the stop event.
+   */
+  IDE_DEBUGGER_STOP_CATCH,
+  IDE_DEBUGGER_STOP_UNKNOWN,
+} IdeDebuggerStopReason;
+
+#define IDE_TYPE_DEBUGGER_STOP_REASON    (ide_debugger_stop_reason_get_type())
+#define IDE_IS_DEBUGGER_STOP_REASON(r)   (((gint)r >= 0) && ((gint)r) <= IDE_DEBUGGER_STOP_UNKNOWN)
+#define IDE_DEBUGGER_STOP_IS_TERMINAL(r) (((r) == IDE_DEBUGGER_STOP_EXITED) || \
+                                          ((r) == IDE_DEBUGGER_STOP_EXITED_NORMALLY) || \
+                                          ((r) == IDE_DEBUGGER_STOP_EXITED_SIGNALED))
+
+/**
+ * IdeDebuggerBreakMode:
+ * @IDE_DEBUGGER_BREAK_BREAKPOINT: A simple breakpoint that stops the debugger
+ *   when reaching a given location.
+ * @IDE_DEBUGGER_BREAK_COUNTPOINT: A counter that is incremented when the
+ *   debugger reaches a breakpoint.
+ * @IDE_DEBUGGER_BREAK_WATCHPOINT: A breakpoint that is conditional on the
+ *   specification matching.
+ *
+ * The type of breakpoint.
+ */
+typedef enum
+{
+  IDE_DEBUGGER_BREAK_BREAKPOINT,
+  IDE_DEBUGGER_BREAK_COUNTPOINT,
+  IDE_DEBUGGER_BREAK_WATCHPOINT,
+} IdeDebuggerBreakMode;
+
+#define IDE_TYPE_DEBUGGER_BREAK_MODE  (ide_debugger_break_mode_get_type())
+#define IDE_IS_DEBUGGER_BREAK_MODE(m) (((gint)m >= 0) && ((gint)m) <= IDE_DEBUGGER_BREAK_WATCHPOINT)
+
+
+/**
+ * IdeDebuggerBreakpointChange:
+ * @IDE_DEBUGGER_BREAKPOINT_CHANGE_ENABLED: change the enabled state
+ *
+ * Describes the type of modification to perform on a breakpoint.
+ */
+typedef enum
+{
+  IDE_DEBUGGER_BREAKPOINT_CHANGE_ENABLED = 1,
+} IdeDebuggerBreakpointChange;
+
+#define IDE_TYPE_DEBUGGER_BREAKPOINT_CHANGE  (ide_debugger_breakpoint_change_get_type())
+#define IDE_IS_DEBUGGER_BREAKPOINT_CHANGE(c) (((gint)c > 0) && ((gint)c) <= 
IDE_DEBUGGER_BREAKPOINT_CHANGE_ENABLED)
+
+
+/**
+ * IdeDebuggerDisposition:
+ * @IDE_DEBUGGER_DISPOSITION_KEEP: the breakpoint will be kept after
+ *   the next stop. This generally means the breakpoint is persistent until
+ *   removed by the user.
+ * @IDE_DEBUGGER_DISPOSITION_DELETE_NEXT_HIT: The breakpoint will be removed
+ *   after the next time it is hit.
+ * @IDE_DEBUGGER_DISPOSITION_DELETE_NEXT_STOP: The breakpoint will be removed
+ *   the next time the debugger stops, even if not hit.
+ * @IDE_DEBUGGER_DISPOSITION_DISABLE: The breakpoint is currently disabled.
+ *
+ * The disposition determines what should happen to the breakpoint at the next
+ * stop of the debugger.
+ */
+typedef enum
+{
+  IDE_DEBUGGER_DISPOSITION_KEEP,
+  IDE_DEBUGGER_DISPOSITION_DISABLE,
+  IDE_DEBUGGER_DISPOSITION_DELETE_NEXT_HIT,
+  IDE_DEBUGGER_DISPOSITION_DELETE_NEXT_STOP,
+} IdeDebuggerDisposition;
+
+#define IDE_TYPE_DEBUGGER_DISPOSITION  (ide_debugger_disposition_get_type())
+#define IDE_IS_DEBUGGER_DISPOSITION(d) (((gint)d >= 0) && ((gint)d) <= 
IDE_DEBUGGER_DISPOSITION_DELETE_NEXT_STOP)
+
+
+typedef guint64 IdeDebuggerAddress;
+
+#define IDE_DEBUGGER_ADDRESS_INVALID (0)
+
+IdeDebuggerAddress ide_debugger_address_parse (const gchar *string);
+
+typedef struct
+{
+  IdeDebuggerAddress from;
+  IdeDebuggerAddress to;
+} IdeDebuggerAddressRange;
+
+#define IDE_TYPE_DEBUGGER_ADDRESS_RANGE (ide_debugger_address_range_get_type())
+
+
+GType ide_debugger_stream_get_type            (void);
+GType ide_debugger_movement_get_type          (void);
+GType ide_debugger_stop_reason_get_type       (void);
+GType ide_debugger_break_mode_get_type        (void);
+GType ide_debugger_disposition_get_type       (void);
+GType ide_debugger_address_range_get_type     (void);
+GType ide_debugger_breakpoint_change_get_type (void);
+
+
+IdeDebuggerAddressRange *ide_debugger_address_range_copy (const IdeDebuggerAddressRange *range);
+void                     ide_debugger_address_range_free (IdeDebuggerAddressRange       *range);
+
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger-variable.c b/libide/debugger/ide-debugger-variable.c
new file mode 100644
index 0000000..2bcafa5
--- /dev/null
+++ b/libide/debugger/ide-debugger-variable.c
@@ -0,0 +1,261 @@
+/* ide-debugger-variable.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 "ide-debugger-variable"
+
+#include "ide-debugger-variable.h"
+
+typedef struct
+{
+  gchar *name;
+  gchar *type_name;
+  gchar *value;
+  guint  has_children;
+} IdeDebuggerVariablePrivate;
+
+enum {
+  PROP_0,
+  PROP_HAS_CHILDREN,
+  PROP_NAME,
+  PROP_TYPE_NAME,
+  PROP_VALUE,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeDebuggerVariable, ide_debugger_variable, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_debugger_variable_finalize (GObject *object)
+{
+  IdeDebuggerVariable *self = (IdeDebuggerVariable *)object;
+  IdeDebuggerVariablePrivate *priv = ide_debugger_variable_get_instance_private (self);
+
+  g_clear_pointer (&priv->name, g_free);
+  g_clear_pointer (&priv->type_name, g_free);
+  g_clear_pointer (&priv->value, g_free);
+
+  G_OBJECT_CLASS (ide_debugger_variable_parent_class)->finalize (object);
+}
+
+static void
+ide_debugger_variable_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  IdeDebuggerVariable *self = IDE_DEBUGGER_VARIABLE (object);
+  IdeDebuggerVariablePrivate *priv = ide_debugger_variable_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_HAS_CHILDREN:
+      g_value_set_boolean (value, ide_debugger_variable_get_has_children (self));
+      break;
+
+    case PROP_NAME:
+      g_value_set_string (value, priv->name);
+      break;
+
+    case PROP_TYPE_NAME:
+      g_value_set_string (value, ide_debugger_variable_get_type_name (self));
+      break;
+
+    case PROP_VALUE:
+      g_value_set_string (value, ide_debugger_variable_get_value (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_variable_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  IdeDebuggerVariable *self = IDE_DEBUGGER_VARIABLE (object);
+  IdeDebuggerVariablePrivate *priv = ide_debugger_variable_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_HAS_CHILDREN:
+      ide_debugger_variable_set_has_children (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_NAME:
+      priv->name = g_value_dup_string (value);
+      break;
+
+    case PROP_TYPE_NAME:
+      ide_debugger_variable_set_type_name (self, g_value_get_string (value));
+      break;
+
+    case PROP_VALUE:
+      ide_debugger_variable_set_value (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_variable_class_init (IdeDebuggerVariableClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_debugger_variable_finalize;
+  object_class->get_property = ide_debugger_variable_get_property;
+  object_class->set_property = ide_debugger_variable_set_property;
+
+  properties [PROP_HAS_CHILDREN] =
+    g_param_spec_boolean ("has-children",
+                          "Has Children",
+                          "If the variable has children variables such as struct members",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name",
+                         "Name",
+                         "The name of the variable",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TYPE_NAME] =
+    g_param_spec_string ("type-name",
+                         "Type Name",
+                         "The name of the variable's type",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_VALUE] =
+    g_param_spec_string ("value",
+                         "Value",
+                         "The value of the variable",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_debugger_variable_init (IdeDebuggerVariable *self)
+{
+}
+
+IdeDebuggerVariable *
+ide_debugger_variable_new (const gchar *name)
+{
+  return g_object_new (IDE_TYPE_DEBUGGER_VARIABLE,
+                       "name", name,
+                       NULL);
+}
+
+const gchar *
+ide_debugger_variable_get_name (IdeDebuggerVariable *self)
+{
+  IdeDebuggerVariablePrivate *priv = ide_debugger_variable_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_VARIABLE (self), NULL);
+
+  return priv->name;
+}
+
+const gchar *
+ide_debugger_variable_get_type_name (IdeDebuggerVariable *self)
+{
+  IdeDebuggerVariablePrivate *priv = ide_debugger_variable_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_VARIABLE (self), NULL);
+
+  return priv->type_name;
+}
+
+void
+ide_debugger_variable_set_type_name (IdeDebuggerVariable *self,
+                                     const gchar         *type_name)
+{
+  IdeDebuggerVariablePrivate *priv = ide_debugger_variable_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_VARIABLE (self));
+
+  if (g_strcmp0 (priv->type_name, type_name) != 0)
+    {
+      g_free (priv->type_name);
+      priv->type_name = g_strdup (type_name);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TYPE_NAME]);
+    }
+}
+
+const gchar *
+ide_debugger_variable_get_value (IdeDebuggerVariable *self)
+{
+  IdeDebuggerVariablePrivate *priv = ide_debugger_variable_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_VARIABLE (self), NULL);
+
+  return priv->value;
+}
+
+void
+ide_debugger_variable_set_value (IdeDebuggerVariable *self,
+                                 const gchar         *value)
+{
+  IdeDebuggerVariablePrivate *priv = ide_debugger_variable_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_VARIABLE (self));
+
+  if (g_strcmp0 (priv->value, value) != 0)
+    {
+      g_free (priv->value);
+      priv->value = g_strdup (value);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VALUE]);
+    }
+}
+
+gboolean
+ide_debugger_variable_get_has_children (IdeDebuggerVariable *self)
+{
+  IdeDebuggerVariablePrivate *priv = ide_debugger_variable_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER_VARIABLE (self), FALSE);
+
+  return priv->has_children;
+}
+
+void
+ide_debugger_variable_set_has_children (IdeDebuggerVariable *self,
+                                        gboolean             has_children)
+{
+  IdeDebuggerVariablePrivate *priv = ide_debugger_variable_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER_VARIABLE (self));
+
+  has_children = !!has_children;
+
+  if (has_children != priv->has_children)
+    {
+      priv->has_children = has_children;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_CHILDREN]);
+    }
+}
diff --git a/libide/debugger/ide-debugger-variable.h b/libide/debugger/ide-debugger-variable.h
new file mode 100644
index 0000000..37c039d
--- /dev/null
+++ b/libide/debugger/ide-debugger-variable.h
@@ -0,0 +1,55 @@
+/* ide-debugger-variable.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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER_VARIABLE (ide_debugger_variable_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeDebuggerVariable, ide_debugger_variable, IDE, DEBUGGER_VARIABLE, GObject)
+
+struct _IdeDebuggerVariableClass
+{
+  GObjectClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+IdeDebuggerVariable *ide_debugger_variable_new              (const gchar         *name);
+const gchar         *ide_debugger_variable_get_name         (IdeDebuggerVariable *self);
+const gchar         *ide_debugger_variable_get_type_name    (IdeDebuggerVariable *self);
+void                 ide_debugger_variable_set_type_name    (IdeDebuggerVariable *self,
+                                                             const gchar         *type_name);
+const gchar         *ide_debugger_variable_get_value        (IdeDebuggerVariable *self);
+void                 ide_debugger_variable_set_value        (IdeDebuggerVariable *self,
+                                                             const gchar         *value);
+gboolean             ide_debugger_variable_get_has_children (IdeDebuggerVariable *self);
+void                 ide_debugger_variable_set_has_children (IdeDebuggerVariable *self,
+                                                             gboolean             has_children);
+
+G_END_DECLS
diff --git a/libide/debugger/ide-debugger.c b/libide/debugger/ide-debugger.c
new file mode 100644
index 0000000..7ebf0f5
--- /dev/null
+++ b/libide/debugger/ide-debugger.c
@@ -0,0 +1,1948 @@
+/* ide-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 "ide-debugger"
+
+#include "ide-debugger.h"
+#include "ide-debugger-address-map.h"
+#include "ide-debugger-private.h"
+
+/**
+ * SECTION:ide-debugger
+ * @title: IdeDebugger
+ * @short_description: Base class for debugger implementations
+ *
+ * The IdeDebugger abstract base class is used by debugger implementations.
+ * They should bridge their backend-specific features into those supported
+ * by the API using the series of "emit" functions provided as part of
+ * this class.
+ *
+ * For example, when the inferior creates a new thread, the debugger
+ * implementation should call ide_debugger_emit_thread_added().
+ *
+ * Since: 3.26
+ */
+
+typedef struct
+{
+  gchar                 *display_name;
+  GListStore            *breakpoints;
+  GListStore            *threads;
+  GListStore            *thread_groups;
+  IdeDebuggerThread     *selected;
+  IdeDebuggerAddressMap *map;
+  guint                  has_started : 1;
+  guint                  is_running : 1;
+} IdeDebuggerPrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (IdeDebugger, ide_debugger, G_TYPE_OBJECT,
+                                  G_ADD_PRIVATE (IdeDebugger)
+                                  G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP,
+                                                         _ide_debugger_class_init_actions))
+
+enum {
+  PROP_0,
+  PROP_DISPLAY_NAME,
+  PROP_SELECTED_THREAD,
+  N_PROPS
+};
+
+enum {
+  LOG,
+
+  THREAD_GROUP_ADDED,
+  THREAD_GROUP_EXITED,
+  THREAD_GROUP_REMOVED,
+  THREAD_GROUP_STARTED,
+
+  THREAD_ADDED,
+  THREAD_REMOVED,
+  THREAD_SELECTED,
+
+  BREAKPOINT_ADDED,
+  BREAKPOINT_MODIFIED,
+  BREAKPOINT_REMOVED,
+
+  RUNNING,
+  STOPPED,
+
+  LIBRARY_LOADED,
+  LIBRARY_UNLOADED,
+
+  N_SIGNALS
+};
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static void
+ide_debugger_real_breakpoint_added (IdeDebugger           *self,
+                                    IdeDebuggerBreakpoint *breakpoint)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+
+  g_debug ("Added breakpoint %s",
+           ide_debugger_breakpoint_get_id (breakpoint));
+
+  g_list_store_insert_sorted (priv->breakpoints,
+                              breakpoint,
+                              (GCompareDataFunc)ide_debugger_breakpoint_compare,
+                              NULL);
+}
+
+static void
+ide_debugger_real_breakpoint_removed (IdeDebugger           *self,
+                                      IdeDebuggerBreakpoint *breakpoint)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+  guint n_items;
+
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+
+  g_debug ("Removed breakpoint %s",
+           ide_debugger_breakpoint_get_id (breakpoint));
+
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (priv->breakpoints));
+
+  for (guint i = 0; i < n_items; i++)
+    {
+      g_autoptr(IdeDebuggerBreakpoint) element = NULL;
+
+      element = g_list_model_get_item (G_LIST_MODEL (priv->breakpoints), i);
+
+      g_assert (element != NULL);
+      g_assert (IDE_IS_DEBUGGER_BREAKPOINT (element));
+
+      if (breakpoint == element)
+        break;
+
+      if (ide_debugger_breakpoint_compare (breakpoint, element) == 0)
+        {
+          g_list_store_remove (priv->breakpoints, i);
+          break;
+        }
+    }
+}
+
+static void
+ide_debugger_real_breakpoint_modified (IdeDebugger           *self,
+                                       IdeDebuggerBreakpoint *breakpoint)
+{
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+
+  g_debug ("Modified breakpoint %s (%s)",
+           ide_debugger_breakpoint_get_id (breakpoint),
+           ide_debugger_breakpoint_get_enabled (breakpoint) ?  "enabled" : "disabled");
+
+  /*
+   * If we add API to GListStore, we could make this a single
+   * operation instead of 2 signal emissions.
+   */
+  ide_debugger_real_breakpoint_removed (self, breakpoint);
+  ide_debugger_real_breakpoint_added (self, breakpoint);
+}
+
+static void
+ide_debugger_real_thread_group_added (IdeDebugger            *self,
+                                      IdeDebuggerThreadGroup *thread_group)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_THREAD_GROUP (thread_group));
+
+  g_debug ("Added thread group %s",
+           ide_debugger_thread_group_get_id (thread_group));
+
+  g_list_store_insert_sorted (priv->thread_groups,
+                              thread_group,
+                              (GCompareDataFunc)ide_debugger_thread_group_compare,
+                              NULL);
+}
+
+static void
+ide_debugger_real_thread_group_removed (IdeDebugger            *self,
+                                        IdeDebuggerThreadGroup *thread_group)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+  guint n_items;
+
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_THREAD_GROUP (thread_group));
+
+  g_debug ("Removed thread group %s",
+           ide_debugger_thread_group_get_id (thread_group));
+
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (priv->thread_groups));
+
+  for (guint i = 0; i < n_items; i++)
+    {
+      g_autoptr(IdeDebuggerThreadGroup) element = NULL;
+
+      element = g_list_model_get_item (G_LIST_MODEL (priv->thread_groups), i);
+
+      g_assert (element != NULL);
+      g_assert (IDE_IS_DEBUGGER_THREAD_GROUP (element));
+
+      if (thread_group == element)
+        break;
+
+      if (ide_debugger_thread_group_compare (thread_group, element) == 0)
+        {
+          g_list_store_remove (priv->thread_groups, i);
+          break;
+        }
+    }
+}
+
+static void
+ide_debugger_real_thread_added (IdeDebugger       *self,
+                                IdeDebuggerThread *thread)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_THREAD (thread));
+
+  g_debug ("Added thread %s", ide_debugger_thread_get_id (thread));
+
+  g_list_store_insert_sorted (priv->threads,
+                              thread,
+                              (GCompareDataFunc)ide_debugger_thread_compare,
+                              NULL);
+}
+
+static void
+ide_debugger_real_thread_removed (IdeDebugger       *self,
+                                  IdeDebuggerThread *thread)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+  guint n_items;
+
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_THREAD (thread));
+
+  g_debug ("Removed thread %s", ide_debugger_thread_get_id (thread));
+
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (priv->threads));
+
+  for (guint i = 0; i < n_items; i++)
+    {
+      g_autoptr(IdeDebuggerThread) element = NULL;
+
+      element = g_list_model_get_item (G_LIST_MODEL (priv->threads), i);
+
+      g_assert (element != NULL);
+      g_assert (IDE_IS_DEBUGGER_THREAD (element));
+
+      if (thread == element)
+        break;
+
+      if (ide_debugger_thread_compare (thread, element) == 0)
+        {
+          g_list_store_remove (priv->threads, i);
+          break;
+        }
+    }
+}
+
+static void
+ide_debugger_real_running (IdeDebugger *self)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_assert (IDE_IS_DEBUGGER (self));
+
+  priv->is_running = TRUE;
+  priv->has_started = TRUE;
+
+  _ide_debugger_update_actions (self);
+}
+
+static void
+ide_debugger_real_stopped (IdeDebugger           *self,
+                           IdeDebuggerStopReason  stop_reason,
+                           IdeDebuggerBreakpoint *breakpoint)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_STOP_REASON (stop_reason));
+  g_assert (!breakpoint || IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+
+  /* We might need to eventually track this by thread group */
+  priv->is_running = FALSE;
+
+  if (IDE_DEBUGGER_STOP_IS_TERMINAL (stop_reason))
+    priv->has_started = FALSE;
+
+  _ide_debugger_update_actions (self);
+}
+
+static void
+ide_debugger_real_thread_selected (IdeDebugger       *self,
+                                   IdeDebuggerThread *thread)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_THREAD (thread));
+
+  if (g_set_object (&priv->selected, thread))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SELECTED_THREAD]);
+}
+
+static void
+ide_debugger_finalize (GObject *object)
+{
+  IdeDebugger *self = (IdeDebugger *)object;
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_clear_pointer (&priv->map, ide_debugger_address_map_free);
+  g_clear_pointer (&priv->display_name, g_free);
+  g_clear_object (&priv->breakpoints);
+  g_clear_object (&priv->threads);
+  g_clear_object (&priv->thread_groups);
+  g_clear_object (&priv->selected);
+
+  G_OBJECT_CLASS (ide_debugger_parent_class)->finalize (object);
+}
+
+static gboolean
+ide_debugger_real_get_can_move (IdeDebugger         *self,
+                                IdeDebuggerMovement  movement)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), FALSE);
+  g_return_val_if_fail (IDE_IS_DEBUGGER_MOVEMENT (movement), FALSE);
+
+  switch (movement)
+    {
+    case IDE_DEBUGGER_MOVEMENT_START:
+      return !ide_debugger_get_is_running (self);
+
+    case IDE_DEBUGGER_MOVEMENT_CONTINUE:
+    case IDE_DEBUGGER_MOVEMENT_STEP_IN:
+    case IDE_DEBUGGER_MOVEMENT_STEP_OVER:
+      return _ide_debugger_get_has_started (self) &&
+             !ide_debugger_get_is_running (self);
+
+    default:
+      g_return_val_if_reached (FALSE);
+    }
+}
+
+static void
+ide_debugger_real_library_loaded (IdeDebugger        *self,
+                                  IdeDebuggerLibrary *library)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+  IdeDebuggerAddressMapEntry entry = { 0 };
+  GPtrArray *ranges;
+
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_LIBRARY (library));
+
+  /* We don't yet have this information */
+  entry.offset = 0;
+  entry.filename = ide_debugger_library_get_target_name (library);
+
+  ranges = ide_debugger_library_get_ranges (library);
+
+  if (ranges != NULL)
+    {
+      for (guint i = 0; i < ranges->len; i++)
+        {
+          const IdeDebuggerAddressRange *range = g_ptr_array_index (ranges, i);
+
+          entry.start = range->from;
+          entry.end = range->to;
+
+          ide_debugger_address_map_insert (priv->map, &entry);
+        }
+    }
+}
+
+static void
+ide_debugger_real_library_unloaded (IdeDebugger        *self,
+                                    IdeDebuggerLibrary *library)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+  GPtrArray *ranges;
+
+  g_assert (IDE_IS_DEBUGGER (self));
+  g_assert (IDE_IS_DEBUGGER_LIBRARY (library));
+
+  ranges = ide_debugger_library_get_ranges (library);
+
+  if (ranges != NULL)
+    {
+      for (guint i = 0; i < ranges->len; i++)
+        {
+          const IdeDebuggerAddressRange *range = g_ptr_array_index (ranges, i);
+
+          ide_debugger_address_map_remove (priv->map, range->from);
+        }
+    }
+}
+
+static void
+ide_debugger_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  IdeDebugger *self = IDE_DEBUGGER (object);
+
+  switch (prop_id)
+    {
+    case PROP_DISPLAY_NAME:
+      g_value_set_string (value, ide_debugger_get_display_name (self));
+      break;
+
+    case PROP_SELECTED_THREAD:
+      g_value_set_object (value, ide_debugger_get_selected_thread (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  IdeDebugger *self = IDE_DEBUGGER (object);
+
+  switch (prop_id)
+    {
+    case PROP_DISPLAY_NAME:
+      ide_debugger_set_display_name (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_debugger_class_init (IdeDebuggerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_debugger_finalize;
+  object_class->get_property = ide_debugger_get_property;
+  object_class->set_property = ide_debugger_set_property;
+
+  klass->breakpoint_added = ide_debugger_real_breakpoint_added;
+  klass->breakpoint_modified = ide_debugger_real_breakpoint_modified;
+  klass->breakpoint_removed = ide_debugger_real_breakpoint_removed;
+  klass->disassemble_async = _ide_debugger_real_disassemble_async;
+  klass->disassemble_finish = _ide_debugger_real_disassemble_finish;
+  klass->get_can_move = ide_debugger_real_get_can_move;
+  klass->interrupt_async = _ide_debugger_real_interrupt_async;
+  klass->interrupt_finish = _ide_debugger_real_interrupt_finish;
+  klass->library_loaded = ide_debugger_real_library_loaded;
+  klass->library_unloaded = ide_debugger_real_library_unloaded;
+  klass->list_frames_async = _ide_debugger_real_list_frames_async;
+  klass->list_frames_finish = _ide_debugger_real_list_frames_finish;
+  klass->list_locals_async = _ide_debugger_real_list_locals_async;
+  klass->list_locals_finish = _ide_debugger_real_list_locals_finish;
+  klass->list_params_async = _ide_debugger_real_list_params_async;
+  klass->list_params_finish = _ide_debugger_real_list_params_finish;
+  klass->list_registers_async = _ide_debugger_real_list_registers_async;
+  klass->list_registers_finish = _ide_debugger_real_list_registers_finish;
+  klass->modify_breakpoint_async = _ide_debugger_real_modify_breakpoint_async;
+  klass->modify_breakpoint_finish = _ide_debugger_real_modify_breakpoint_finish;
+  klass->running = ide_debugger_real_running;
+  klass->send_signal_async = _ide_debugger_real_send_signal_async;
+  klass->send_signal_finish = _ide_debugger_real_send_signal_finish;
+  klass->stopped = ide_debugger_real_stopped;
+  klass->thread_added = ide_debugger_real_thread_added;
+  klass->thread_group_added = ide_debugger_real_thread_group_added;
+  klass->thread_group_removed = ide_debugger_real_thread_group_removed;
+  klass->thread_removed = ide_debugger_real_thread_removed;
+  klass->thread_selected = ide_debugger_real_thread_selected;
+
+  /**
+   * IdeDebugger:display-name:
+   *
+   * The "display-name" property is used by UI to when it is necessary
+   * to display the name of the debugger. You might set this to "GNU Debugger"
+   * or "Python Debugger", etc.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_DISPLAY_NAME] =
+    g_param_spec_string ("display-name",
+                         "Display Name",
+                         "The name of the debugger to use in various UI components",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeDebugger:selected-thread:
+   *
+   * The currently selected thread.
+   *
+   * Since: 3.26
+   */
+  properties [PROP_SELECTED_THREAD] =
+    g_param_spec_object ("thread-selected",
+                         "Thread Selected",
+                         "The currently selected thread",
+                         IDE_TYPE_DEBUGGER_THREAD,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  /**
+   * IdeDebugger::log:
+   * @self: An #IdeDebugger.
+   * @stream: the stream to append to.
+   * @content: the contents for the stream.
+   *
+   * The "log" signal is emitted when there is new content to be
+   * appended to one of the streams.
+   *
+   * Since: 3.26
+   */
+  signals [LOG] =
+    g_signal_new ("log",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, log),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  2,
+                  IDE_TYPE_DEBUGGER_STREAM,
+                  G_TYPE_BYTES | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+  /**
+   * IdeDebugger::thread-group-added:
+   * @self: an #IdeDebugger
+   * @thread_group: an #IdeDebuggerThreadGroup
+   *
+   * This signal is emitted when a thread-group has been added.
+   *
+   * Since: 3.26
+   */
+  signals [THREAD_GROUP_ADDED] =
+    g_signal_new ("thread-group-added",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, thread_group_added),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  1,
+                  IDE_TYPE_DEBUGGER_THREAD_GROUP);
+
+  /**
+   * IdeDebugger::thread-group-removed:
+   * @self: an #IdeDebugger
+   * @thread_group: an #IdeDebuggerThreadGroup
+   *
+   * This signal is emitted when a thread-group has been removed.
+   *
+   * Since: 3.26
+   */
+  signals [THREAD_GROUP_REMOVED] =
+    g_signal_new ("thread-group-removed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, thread_group_removed),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  1,
+                  IDE_TYPE_DEBUGGER_THREAD_GROUP);
+
+  /**
+   * IdeDebugger::thread-group-started:
+   * @self: an #IdeDebugger
+   * @thread_group: an #IdeDebuggerThreadGroup
+   *
+   * This signal is emitted when a thread-group has been started.
+   *
+   * Since: 3.26
+   */
+  signals [THREAD_GROUP_STARTED] =
+    g_signal_new ("thread-group-started",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, thread_group_started),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  1,
+                  IDE_TYPE_DEBUGGER_THREAD_GROUP);
+
+  /**
+   * IdeDebugger::thread-group-exited:
+   * @self: an #IdeDebugger
+   * @thread_group: an #IdeDebuggerThreadGroup
+   *
+   * This signal is emitted when a thread-group has exited.
+   *
+   * Since: 3.26
+   */
+  signals [THREAD_GROUP_EXITED] =
+    g_signal_new ("thread-group-exited",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, thread_group_exited),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, IDE_TYPE_DEBUGGER_THREAD_GROUP);
+
+  /**
+   * IdeDebugger::thread-added:
+   * @self: an #IdeDebugger
+   * @thread: an #IdeDebuggerThread
+   *
+   * The signal is emitted when a thread is added to the inferior.
+   *
+   * Since: 3.26
+   */
+  signals [THREAD_ADDED] =
+    g_signal_new ("thread-added",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, thread_added),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, IDE_TYPE_DEBUGGER_THREAD);
+
+  /**
+   * IdeDebugger::thread-removed:
+   * @self: an #IdeDebugger
+   * @thread: an #IdeDebuggerThread
+   *
+   * The signal is emitted when a thread is removed from the inferior.
+   *
+   * Since: 3.26
+   */
+  signals [THREAD_REMOVED] =
+    g_signal_new ("thread-removed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, thread_removed),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, IDE_TYPE_DEBUGGER_THREAD);
+
+  /**
+   * IdeDebugger::thread-selected:
+   * @self: an #IdeDebugger
+   * @thread: an #IdeDebuggerThread
+   *
+   * The signal is emitted when a thread is selected in the debugger.
+   *
+   * Since: 3.26
+   */
+  signals [THREAD_SELECTED] =
+    g_signal_new ("thread-selected",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, thread_selected),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, IDE_TYPE_DEBUGGER_THREAD);
+
+  /**
+   * IdeDebugger::breakpoint-added:
+   * @self: an #IdeDebugger
+   * @breakpoint: an #IdeDebuggerBreakpoint
+   *
+   * The "breakpoint-added" signal is emitted when a breakpoint has been
+   * added to the debugger.
+   *
+   * Since: 3.26
+   */
+  signals [BREAKPOINT_ADDED] =
+    g_signal_new ("breakpoint-added",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, breakpoint_added),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, IDE_TYPE_DEBUGGER_BREAKPOINT);
+
+  /**
+   * IdeDebugger::breakpoint-removed:
+   * @self: an #IdeDebugger
+   * @breakpoint: an #IdeDebuggerBreakpoint
+   *
+   * The "breakpoint-removed" signal is emitted when a breakpoint has been
+   * removed from the debugger.
+   *
+   * Since: 3.26
+   */
+  signals [BREAKPOINT_REMOVED] =
+    g_signal_new ("breakpoint-removed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, breakpoint_removed),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, IDE_TYPE_DEBUGGER_BREAKPOINT);
+
+  /**
+   * IdeDebugger::breakpoint-modified:
+   * @self: an #IdeDebugger
+   * @breakpoint: an #IdeDebuggerBreakpoint
+   *
+   * The "breakpoint-modified" signal is emitted when a breakpoint has been
+   * modified by the debugger.
+   *
+   * Since: 3.26
+   */
+  signals [BREAKPOINT_MODIFIED] =
+    g_signal_new ("breakpoint-modified",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, breakpoint_modified),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, IDE_TYPE_DEBUGGER_BREAKPOINT);
+
+  /**
+   * IdeDebugger::running:
+   * @self: a #IdeDebugger
+   *
+   * This signal is emitted when the debugger starts or resumes executing
+   * the inferior.
+   *
+   * Since: 3.26
+   */
+  signals [RUNNING] =
+    g_signal_new ("running",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, running),
+                  NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  /**
+   * IdeDebugger::stopped:
+   * @self: a #IdeDebugger
+   * @stop_reason: An #IdeDebuggerStopReason
+   * @breakpoint: (nullable): An #IdeDebuggerBreakpoint if any
+   *
+   * This signal is emitted when the debugger has stopped executing the
+   * inferior for a variety of reasons.
+   *
+   * If possible, the debugger implementation will provide the breakpoint of
+   * the location the debugger stopped. That location may not always be
+   * representable by source in the project (such as memory address based
+   * breakpoints).
+   *
+   * Since: 3.26
+   */
+  signals [STOPPED] =
+    g_signal_new ("stopped",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, stopped),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  2,
+                  IDE_TYPE_DEBUGGER_STOP_REASON,
+                  IDE_TYPE_DEBUGGER_BREAKPOINT);
+
+  /**
+   * IdeDebugger::library-loaded:
+   * @self: An #IdeDebugger
+   * @library: An #IdeDebuggerLibrary
+   *
+   * This signal is emitted when a library has been loaded by the debugger.
+   *
+   * Since: 3.26
+   */
+  signals [LIBRARY_LOADED] =
+    g_signal_new ("library-loaded",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, library_loaded),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, IDE_TYPE_DEBUGGER_LIBRARY);
+
+  /**
+   * IdeDebugger::library-unloaded:
+   * @self: An #IdeDebugger
+   * @library: An #IdeDebuggerLibrary
+   *
+   * This signal is emitted when a library has been unloaded by the debugger.
+   * Generally, this means that the library was a module and loaded in such a
+   * way that allowed unloading.
+   *
+   * Since: 3.26
+   */
+  signals [LIBRARY_UNLOADED] =
+    g_signal_new ("library-unloaded",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeDebuggerClass, library_unloaded),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, IDE_TYPE_DEBUGGER_LIBRARY);
+}
+
+static void
+ide_debugger_init (IdeDebugger *self)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  priv->breakpoints = g_list_store_new (IDE_TYPE_DEBUGGER_BREAKPOINT);
+  priv->map = ide_debugger_address_map_new ();
+  priv->thread_groups = g_list_store_new (IDE_TYPE_DEBUGGER_THREAD_GROUP);
+  priv->threads = g_list_store_new (IDE_TYPE_DEBUGGER_THREAD);
+}
+
+/**
+ * ide_debugger_get_display_name:
+ * @self: a #IdeDebugger
+ *
+ * Gets the display name for the debugger as the user should see it in various
+ * UI components.
+ *
+ * Returns: The display name for the debugger
+ *
+ * Since: 3.26
+ */
+const gchar *
+ide_debugger_get_display_name (IdeDebugger *self)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), NULL);
+
+  return priv->display_name;
+}
+
+/**
+ * ide_debugger_set_display_name:
+ * @self: a #IdeDebugger
+ *
+ * Sets the #IdeDebugger:display-name property.
+ */
+void
+ide_debugger_set_display_name (IdeDebugger *self,
+                               const gchar *display_name)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+
+  if (g_strcmp0 (priv->display_name, display_name) != 0)
+    {
+      g_free (priv->display_name);
+      priv->display_name = g_strdup (display_name);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISPLAY_NAME]);
+    }
+}
+
+/**
+ * ide_debugger_get_can_move:
+ * @self: a #IdeDebugger
+ * @movement: the movement to check
+ *
+ * Checks to see if the debugger can make the movement matching @movement.
+ *
+ * Returns: %TRUE if @movement can be performed.
+ *
+ * Since: 3.26
+ */
+gboolean
+ide_debugger_get_can_move (IdeDebugger         *self,
+                           IdeDebuggerMovement  movement)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), FALSE);
+
+  if (IDE_DEBUGGER_GET_CLASS (self)->get_can_move)
+    return IDE_DEBUGGER_GET_CLASS (self)->get_can_move (self, movement);
+
+  return FALSE;
+}
+
+/**
+ * ide_debugger_move_async:
+ * @self: a #IdeDebugger
+ * @movement: An #IdeDebuggerMovement
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: (scope async) (closure user_data): A callback to call upon
+ *   completion of the operation.
+ * @user_data: user data for @callback
+ *
+ * Advances the debugger to the next breakpoint or until the debugger stops.
+ * @movement should describe the type of movement to perform.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_move_async (IdeDebugger         *self,
+                         IdeDebuggerMovement  movement,
+                         GCancellable        *cancellable,
+                         GAsyncReadyCallback  callback,
+                         gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_MOVEMENT (movement));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEBUGGER_GET_CLASS (self)->move_async (self, movement, cancellable, callback, user_data);
+}
+
+/**
+ * ide_debugger_move_finish:
+ * @self: a #IdeDebugger
+ * @result: A #GAsyncResult provided to the callback
+ * @error: A location for a #GError, or %NULL
+ *
+ * Notifies that the movement request has been submitted to the debugger.
+ *
+ * Note that this does not indicate that the movement has completed successfully,
+ * only that the command has be submitted.
+ *
+ * Returns: %TRUE if successful, otherwise %FALSE
+ *
+ * Since: 3.26
+ */
+gboolean
+ide_debugger_move_finish (IdeDebugger   *self,
+                          GAsyncResult  *result,
+                          GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return IDE_DEBUGGER_GET_CLASS (self)->move_finish (self, result, error);
+}
+
+/**
+ * ide_debugger_emit_log:
+ * @self: a #IdeDebugger
+ *
+ * Emits the "log" signal.
+ *
+ * Debugger implementations should use this to notify any listeners
+ * that incoming log information has been recieved.
+ *
+ * Use the #IdeDebuggerStream to denote the particular stream.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_log (IdeDebugger       *self,
+                       IdeDebuggerStream  stream,
+                       GBytes            *content)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_STREAM (stream));
+  g_return_if_fail (content != NULL);
+
+  g_signal_emit (self, signals [LOG], 0, stream, content);
+}
+
+/**
+ * ide_debugger_thread_group_added:
+ * @self: an #IdeDebugger
+ * @thread_group: an #IdeDebuggerThreadGroup
+ *
+ * Debugger implementations should call this to notify that a thread group has
+ * been added to the inferior.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_thread_group_added (IdeDebugger            *self,
+                                      IdeDebuggerThreadGroup *thread_group)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD_GROUP (thread_group));
+
+  g_signal_emit (self, signals [THREAD_GROUP_ADDED], 0, thread_group);
+}
+
+/**
+ * ide_debugger_thread_group_removed:
+ * @self: an #IdeDebugger
+ * @thread_group: an #IdeDebuggerThreadGroup
+ *
+ * Debugger implementations should call this to notify that a thread group has
+ * been removed from the inferior.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_thread_group_removed (IdeDebugger            *self,
+                                        IdeDebuggerThreadGroup *thread_group)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD_GROUP (thread_group));
+
+  g_signal_emit (self, signals [THREAD_GROUP_REMOVED], 0, thread_group);
+}
+
+/**
+ * ide_debugger_thread_group_started:
+ * @self: an #IdeDebugger
+ * @thread_group: an #IdeDebuggerThreadGroup
+ *
+ * Debugger implementations should call this to notify that a thread group has
+ * started executing.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_thread_group_started (IdeDebugger            *self,
+                                        IdeDebuggerThreadGroup *thread_group)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD_GROUP (thread_group));
+
+  g_signal_emit (self, signals [THREAD_GROUP_STARTED], 0, thread_group);
+}
+
+/**
+ * ide_debugger_thread_group_exited:
+ * @self: an #IdeDebugger
+ * @thread_group: an #IdeDebuggerThreadGroup
+ *
+ * Debugger implementations should call this to notify that a thread group has
+ * exited.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_thread_group_exited (IdeDebugger            *self,
+                                       IdeDebuggerThreadGroup *thread_group)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD_GROUP (thread_group));
+
+  g_signal_emit (self, signals [THREAD_GROUP_EXITED], 0, thread_group);
+}
+
+/**
+ * ide_debugger_emit_thread_added:
+ * @self: an #IdeDebugger
+ * @thread: an #IdeDebuggerThread
+ *
+ * Emits the #IdeDebugger::thread-added signal notifying that a new thread
+ * has been added to the inferior.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_thread_added (IdeDebugger       *self,
+                                IdeDebuggerThread *thread)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD (thread));
+
+  g_signal_emit (self, signals [THREAD_ADDED], 0, thread);
+}
+
+/**
+ * ide_debugger_emit_thread_removed:
+ * @self: an #IdeDebugger
+ * @thread: an #IdeDebuggerThread
+ *
+ * Emits the #IdeDebugger::thread-removed signal notifying that a thread has
+ * been removed to the inferior.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_thread_removed (IdeDebugger       *self,
+                                  IdeDebuggerThread *thread)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD (thread));
+
+  g_signal_emit (self, signals [THREAD_REMOVED], 0, thread);
+}
+
+/**
+ * ide_debugger_emit_thread_selected:
+ * @self: an #IdeDebugger
+ * @thread: an #IdeDebuggerThread
+ *
+ * Emits the #IdeDebugger::thread-selected signal notifying that a thread
+ * has been set as the current debugging thread.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_thread_selected (IdeDebugger       *self,
+                                   IdeDebuggerThread *thread)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD (thread));
+
+  g_signal_emit (self, signals [THREAD_SELECTED], 0, thread);
+}
+
+/**
+ * ide_debugger_emit_breakpoint_added:
+ * @self: an #IdeDebugger
+ * @breakpoint: an #IdeDebuggerBreakpoint
+ *
+ * Emits the #IdeDebugger::breakpoint-added signal.
+ *
+ * Debugger implementations should call this when a new breakpoint
+ * has been registered with the debugger.
+ *
+ * If a breakpoint has changed, you should use
+ * ide_debugger_emit_breakpoint_modified() to notify of the modification.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_breakpoint_added (IdeDebugger           *self,
+                                    IdeDebuggerBreakpoint *breakpoint)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+
+  g_signal_emit (self, signals [BREAKPOINT_ADDED], 0, breakpoint);
+}
+
+/**
+ * ide_debugger_emit_breakpoint_removed:
+ * @self: an #IdeDebugger
+ * @breakpoint: an #IdeDebuggerBreakpoint
+ *
+ * Emits the #IdeDebugger::breakpoint-removed signal.
+ *
+ * Debugger implementations should call this when a breakpoint has been removed
+ * either manually or automatically by the debugger.
+ *
+ * If a breakpoint has changed, you should use
+ * ide_debugger_emit_breakpoint_modified() to notify of the modification.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_breakpoint_removed (IdeDebugger           *self,
+                                      IdeDebuggerBreakpoint *breakpoint)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+
+  g_signal_emit (self, signals [BREAKPOINT_REMOVED], 0, breakpoint);
+}
+
+/**
+ * ide_debugger_emit_breakpoint_modified:
+ * @self: an #IdeDebugger
+ * @breakpoint: an #IdeDebuggerBreakpoint
+ *
+ * Emits the #IdeDebugger::breakpoint-modified signal.
+ *
+ * Debugger implementations should call this when a breakpoint has changed
+ * in the underlying debugger.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_breakpoint_modified (IdeDebugger           *self,
+                                       IdeDebuggerBreakpoint *breakpoint)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+
+  g_signal_emit (self, signals [BREAKPOINT_MODIFIED], 0, breakpoint);
+}
+
+/**
+ * ide_debugger_emit_running:
+ * @self: an #IdeDebugger
+ *
+ * Emits the "running" signal.
+ *
+ * Debugger implementations should call this when the debugger has started
+ * or restarted executing the inferior.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_running (IdeDebugger *self)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+
+  g_signal_emit (self, signals [RUNNING], 0);
+}
+
+/**
+ * ide_debugger_emit_stopped:
+ * @self: an #IdeDebugger
+ * @stop_reason: the reason the debugger stopped
+ * @breakpoint: the breakpoint representing the stop location
+ *
+ * Emits the "stopped" signal.
+ *
+ * Debugger implementations should call this when the debugger has stopped
+ * and include the reason and location of the stop.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_stopped (IdeDebugger           *self,
+                           IdeDebuggerStopReason  stop_reason,
+                           IdeDebuggerBreakpoint *breakpoint)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_STOP_REASON (stop_reason));
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+
+  g_signal_emit (self, signals [STOPPED], 0, stop_reason, breakpoint);
+}
+
+/**
+ * ide_debugger_emit_library_loaded:
+ * @self: an #IdeDebugger
+ * @library: an #IdeDebuggerLibrary
+ *
+ * Emits the "library-loaded" signal.
+ *
+ * Debugger implementations should call this when the debugger has loaded
+ * a new library.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_library_loaded (IdeDebugger        *self,
+                                  IdeDebuggerLibrary *library)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_LIBRARY (library));
+
+  g_signal_emit (self, signals [LIBRARY_LOADED], 0, library);
+}
+
+/**
+ * ide_debugger_emit_library_unloaded:
+ * @self: an #IdeDebugger
+ * @library: an #IdeDebuggerLibrary
+ *
+ * Emits the "library-unloaded" signal.
+ *
+ * Debugger implementations should call this when the debugger has unloaded a
+ * library.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_emit_library_unloaded (IdeDebugger        *self,
+                                    IdeDebuggerLibrary *library)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_LIBRARY (library));
+
+  g_signal_emit (self, signals [LIBRARY_UNLOADED], 0, library);
+}
+
+/**
+ * ide_debugger_list_breakpoints_async:
+ * @self: An #IdeDebugger
+ * @cancellable: (nullable): A #GCancellable, or %NULL
+ * @callback: a callback to call upon completion
+ * @user_data: user data for @callback
+ *
+ * Asynchronously requests the list of current breakpoints from the debugger.
+ *
+ * #IdeDebugger implementations must implement the virtual function
+ * for this method.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_list_breakpoints_async (IdeDebugger         *self,
+                                     GCancellable        *cancellable,
+                                     GAsyncReadyCallback  callback,
+                                     gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEBUGGER_GET_CLASS (self)->list_breakpoints_async (self, cancellable, callback, user_data);
+}
+
+/**
+ * ide_debugger_list_breakpoints_finish:
+ * @self: An #IdeDebugger
+ * @result: A #GAsyncResult provided to the async callback
+ * @error: a location for a #GError or %NULL
+ *
+ * Gets the list of breakpoints from the debugger.
+ *
+ * Returns: (transfer container) (element-type Ide.DebuggerBreakpoint): A #GPtrArray
+ *   of breakpoints that are registered with the debugger.
+ *
+ * Since: 3.26
+ */
+GPtrArray *
+ide_debugger_list_breakpoints_finish (IdeDebugger   *self,
+                                      GAsyncResult  *result,
+                                      GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), NULL);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+  return IDE_DEBUGGER_GET_CLASS (self)->list_breakpoints_finish (self, result, error);
+}
+
+/**
+ * ide_debugger_insert_breakpoint_async:
+ * @self: An #IdeDebugger
+ * @breakpoint: An #IdeDebuggerBreakpoint
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: an async callback to complete the operation
+ * @user_data: user data for @callback
+ *
+ * Asynchronously requests that a breakpoint is added to the debugger.
+ *
+ * This asynchronous function may complete before the breakpoint has been
+ * registered in the debugger. Debugger implementations will emit
+ * #IdeDebugger::breakpoint-added when a breakpoint has been registered.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_insert_breakpoint_async (IdeDebugger             *self,
+                                      IdeDebuggerBreakpoint   *breakpoint,
+                                      GCancellable            *cancellable,
+                                      GAsyncReadyCallback      callback,
+                                      gpointer                 user_data)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEBUGGER_GET_CLASS (self)->insert_breakpoint_async (self,
+                                                          breakpoint,
+                                                          cancellable,
+                                                          callback,
+                                                          user_data);
+}
+
+/**
+ * ide_debugger_insert_breakpoint_finish:
+ * @self: An #IdeDebugger
+ * @result: A #GAsyncResult or %NULL
+ * @error: A #GError, or %NULL
+ *
+ * Completes a request to asynchronously insert a breakpoint.
+ *
+ * See also: ide_debugger_insert_breakpoint_async()
+ *
+ * Returns: %TRUE if the command was submitted successfully; otherwise %FALSE
+ *   and @error is set.
+ *
+ * Since: 3.26
+ */
+gboolean
+ide_debugger_insert_breakpoint_finish (IdeDebugger   *self,
+                                       GAsyncResult  *result,
+                                       GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_DEBUGGER_GET_CLASS (self)->insert_breakpoint_finish (self, result, error);
+}
+
+/**
+ * ide_debugger_remove_breakpoint_async:
+ * @self: An #IdeDebugger
+ * @breakpoint: An #IdeDebuggerBreakpoint
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: an async callback to complete the operation
+ * @user_data: user data for @callback
+ *
+ * Asynchronously requests that a breakpoint is removed from the debugger.
+ *
+ * This asynchronous function may complete before the breakpoint has been
+ * removed by the debugger. Debugger implementations will emit
+ * #IdeDebugger::breakpoint-removed when a breakpoint has been removed.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_remove_breakpoint_async (IdeDebugger             *self,
+                                      IdeDebuggerBreakpoint   *breakpoint,
+                                      GCancellable            *cancellable,
+                                      GAsyncReadyCallback      callback,
+                                      gpointer                 user_data)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEBUGGER_GET_CLASS (self)->remove_breakpoint_async (self,
+                                                          breakpoint,
+                                                          cancellable,
+                                                          callback,
+                                                          user_data);
+}
+
+/**
+ * ide_debugger_remove_breakpoint_finish:
+ * @self: An #IdeDebugger
+ * @result: A #GAsyncResult or %NULL
+ * @error: A #GError, or %NULL
+ *
+ * Completes a request to asynchronously remove a breakpoint.
+ *
+ * See also: ide_debugger_remove_breakpoint_async()
+ *
+ * Returns: %TRUE if the command was submitted successfully; otherwise %FALSE and @error is set.
+ *
+ * Since: 3.26
+ */
+gboolean
+ide_debugger_remove_breakpoint_finish (IdeDebugger   *self,
+                                       GAsyncResult  *result,
+                                       GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_DEBUGGER_GET_CLASS (self)->remove_breakpoint_finish (self, result, error);
+}
+
+/**
+ * ide_debugger_modify_breakpoint_async:
+ * @self: An #IdeDebugger
+ * @change: An #IdeDebuggerBreakpointChange
+ * @breakpoint: An #IdeDebuggerBreakpoint
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: an async callback to complete the operation
+ * @user_data: user data for @callback
+ *
+ * Asynchronously requests that a breakpoint is modified by the debugger backend.
+ *
+ * Specify @change for how to modify the breakpoint.
+ *
+ * This asynchronous function may complete before the breakpoint has been
+ * modified by the debugger. Debugger implementations will emit
+ * #IdeDebugger::breakpoint-modified when a breakpoint has been removed.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_modify_breakpoint_async (IdeDebugger                 *self,
+                                      IdeDebuggerBreakpointChange  change,
+                                      IdeDebuggerBreakpoint       *breakpoint,
+                                      GCancellable                *cancellable,
+                                      GAsyncReadyCallback          callback,
+                                      gpointer                     user_data)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT_CHANGE (change));
+  g_return_if_fail (IDE_IS_DEBUGGER_BREAKPOINT (breakpoint));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEBUGGER_GET_CLASS (self)->modify_breakpoint_async (self,
+                                                          change,
+                                                          breakpoint,
+                                                          cancellable,
+                                                          callback,
+                                                          user_data);
+}
+
+/**
+ * ide_debugger_modify_breakpoint_finish:
+ * @self: a #IdeDebugger
+ * @result: a #GAsyncResult
+ * @error: a location for a #GError or %NULL
+ *
+ * Completes an asynchronous request to modify a breakpoint.
+ *
+ * Note that this only completes the submission of the request, if you need to
+ * know when the breakpoint has been modified, listen to the
+ * #IdeDebugger::breakpoint-modified signal.
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ *
+ * Since: 3.26
+ */
+gboolean
+ide_debugger_modify_breakpoint_finish (IdeDebugger   *self,
+                                       GAsyncResult  *result,
+                                       GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_DEBUGGER_GET_CLASS (self)->modify_breakpoint_finish (self, result, error);
+}
+
+/**
+ * ide_debugger_get_breakpoints:
+ * @self: An #IdeDebugger
+ *
+ * Gets the breakpoints for the #IdeDebugger.
+ *
+ * Contrast this to ide_debugger_list_breakpoints_async() which will query
+ * the debugger backend for breakpoints. This #GListModel containing
+ * #IdeDebuggerBreakpoint instances is updated as necessary by listening
+ * to various breakpoint related signals on the #IdeDebugger instance.
+ *
+ * This is primarily out of convenience to be used by UI which wants to
+ * display information on breakpoints.
+ *
+ * Returns: (transfer none) (not nullable): A #GListModel of #IdeDebuggerBreakpoint
+ */
+GListModel *
+ide_debugger_get_breakpoints (IdeDebugger *self)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), NULL);
+
+  return G_LIST_MODEL (priv->breakpoints);
+}
+
+/**
+ * ide_debugger_get_thread_groups:
+ * @self: a #IdeDebugger
+ *
+ * Gets the thread groups that have been registered by the debugger.
+ *
+ * The resulting #GListModel accuracy is based on the #IdeDebugger
+ * implementation emitting varous thread-group modification signals correctly.
+ *
+ * Returns: (transfer none) (not nullable): A #GListModel of #IdeDebuggerThreadGroup
+ */
+GListModel *
+ide_debugger_get_thread_groups (IdeDebugger *self)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), NULL);
+
+  return G_LIST_MODEL (priv->thread_groups);
+}
+
+/**
+ * ide_debugger_get_threads:
+ * @self: a #IdeDebugger
+ *
+ * Gets the threads that have been registered by the debugger.
+ *
+ * The resulting #GListModel accuracy is based on the #IdeDebugger
+ * implementation emitting varous thread modification signals correctly.
+ *
+ * Returns: (transfer none) (not nullable): A #GListModel of #IdeDebuggerThread
+ */
+GListModel *
+ide_debugger_get_threads (IdeDebugger *self)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), NULL);
+
+  return G_LIST_MODEL (priv->threads);
+}
+
+void
+ide_debugger_list_frames_async (IdeDebugger         *self,
+                                IdeDebuggerThread   *thread,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD (thread));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEBUGGER_GET_CLASS (self)->list_frames_async (self, thread, cancellable, callback, user_data);
+}
+
+/**
+ * ide_debugger_list_frames_finish:
+ *
+ *
+ *
+ * Returns: (transfer container) (element-type Ide.DebuggerFrame) (nullable): An
+ *   array of debugger frames or %NULL and @error is set.
+ */
+GPtrArray *
+ide_debugger_list_frames_finish (IdeDebugger   *self,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+  return IDE_DEBUGGER_GET_CLASS (self)->list_frames_finish (self, result, error);
+}
+
+/**
+ * ide_debugger_get_selected_thread:
+ * @self: An #IdeDebugger
+ *
+ * Gets the current selected thread by the debugger.
+ *
+ * Returns: (transfer none) (nullable): An #IdeDebuggerThread or %NULL
+ *
+ * Since: 3.26
+ */
+IdeDebuggerThread *
+ide_debugger_get_selected_thread (IdeDebugger *self)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), NULL);
+
+  return priv->selected;
+}
+
+/**
+ * ide_debugger_interrupt_async:
+ * @self: a #IdeDebugger
+ * @thread_group: (nullable): An #IdeDebuggerThreadGroup
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: (closure user_data): a callback to execute upon completion
+ * @user_data: closure data for @callback
+ *
+ * Asynchronously requests that the debugger interrupts execution of a thread
+ * group. Thread groups are a collection of threads that are executed or
+ * stopped together and on gdb on Linux, this is the default for all threads in
+ * the process.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_interrupt_async (IdeDebugger            *self,
+                              IdeDebuggerThreadGroup *thread_group,
+                              GCancellable           *cancellable,
+                              GAsyncReadyCallback     callback,
+                              gpointer                user_data)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (!thread_group || IDE_IS_DEBUGGER_THREAD_GROUP (thread_group));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEBUGGER_GET_CLASS (self)->interrupt_async (self, thread_group, cancellable, callback, user_data);
+}
+
+gboolean
+ide_debugger_interrupt_finish (IdeDebugger   *self,
+                               GAsyncResult  *result,
+                               GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_DEBUGGER_GET_CLASS (self)->interrupt_finish (self, result, error);
+}
+
+gboolean
+ide_debugger_get_is_running (IdeDebugger *self)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), FALSE);
+
+  return priv->is_running;
+}
+
+gboolean
+_ide_debugger_get_has_started (IdeDebugger *self)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), FALSE);
+
+  return priv->has_started;
+}
+
+void
+ide_debugger_send_signal_async (IdeDebugger         *self,
+                                gint                 signum,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEBUGGER_GET_CLASS (self)->send_signal_async (self, signum, cancellable, callback, user_data);
+}
+
+gboolean
+ide_debugger_send_signal_finish (IdeDebugger   *self,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_DEBUGGER_GET_CLASS (self)->send_signal_finish (self, result, error);
+}
+
+/**
+ * ide_debugger_locate_binary_at_address:
+ * @self: a #IdeDebugger
+ * @address: the address within the inferior process space
+ *
+ * Attempts to locate the binary that contains a given address.
+ *
+ * @address should be an address within the inferiors process space.
+ *
+ * This works by keeping track of libraries as they are loaded and unloaded and
+ * their associated file mappings.
+ *
+ * Currently, the filename will match the name in the inferrior mount namespace,
+ * but that may change based on future design changes.
+ *
+ * Returns: the filename of the binary or %NULL
+ *
+ * Since: 3.26
+ */
+const gchar *
+ide_debugger_locate_binary_at_address (IdeDebugger        *self,
+                                       IdeDebuggerAddress  address)
+{
+  IdeDebuggerPrivate *priv = ide_debugger_get_instance_private (self);
+  const IdeDebuggerAddressMapEntry *entry;
+
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), NULL);
+
+  entry = ide_debugger_address_map_lookup (priv->map, address);
+
+  if (entry != NULL)
+    return entry->filename;
+
+  return NULL;
+}
+
+/**
+ * ide_debugger_list_locals_async:
+ * @self: an #IdeDebugger
+ * @thread: an #IdeDebuggerThread
+ * @frame: an #IdeDebuggerFrame
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: A callback to call once the operation has finished
+ * @user_data: user data for @callback
+ *
+ * Requests the debugger backend to list the locals that are available to the
+ * given @frame of @thread.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_list_locals_async (IdeDebugger         *self,
+                                IdeDebuggerThread   *thread,
+                                IdeDebuggerFrame    *frame,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD (thread));
+  g_return_if_fail (IDE_IS_DEBUGGER_FRAME (frame));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEBUGGER_GET_CLASS (self)->list_locals_async (self,
+                                                    thread,
+                                                    frame,
+                                                    cancellable,
+                                                    callback,
+                                                    user_data);
+}
+
+/**
+ * ide_debugger_list_locals_finish:
+ * @self: a #IdeDebugger
+ * @result: A #GAsyncResult
+ * @error: a location for a #GError or %NULL
+ *
+ * Completes an asynchronous request to ide_debugger_list_locals_async().
+ *
+ * Returns: (transfer container) (element-type Ide.DebuggerVariable): A #GPtrArray of
+ *   #IdeDebuggerVariable if successful; otherwise %NULL and error is set.
+ *
+ * Since: 3.26
+ */
+GPtrArray *
+ide_debugger_list_locals_finish (IdeDebugger   *self,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+  return IDE_DEBUGGER_GET_CLASS (self)->list_locals_finish (self, result, error);
+}
+
+/**
+ * ide_debugger_list_params_async:
+ * @self: an #IdeDebugger
+ * @thread: an #IdeDebuggerThread
+ * @frame: an #IdeDebuggerFrame
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: A callback to call once the operation has finished
+ * @user_data: user data for @callback
+ *
+ * Requests the debugger backend to list the parameters to the given stack
+ * frame.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_list_params_async (IdeDebugger         *self,
+                                IdeDebuggerThread   *thread,
+                                IdeDebuggerFrame    *frame,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (IDE_IS_DEBUGGER_THREAD (thread));
+  g_return_if_fail (IDE_IS_DEBUGGER_FRAME (frame));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEBUGGER_GET_CLASS (self)->list_params_async (self,
+                                                    thread,
+                                                    frame,
+                                                    cancellable,
+                                                    callback,
+                                                    user_data);
+}
+
+/**
+ * ide_debugger_list_params_finish:
+ * @self: a #IdeDebugger
+ * @result: A #GAsyncResult
+ * @error: a location for a #GError or %NULL
+ *
+ * Completes an asynchronous request to ide_debugger_list_params_async().
+ *
+ * Returns: (transfer container) (element-type Ide.DebuggerVariable): A #GPtrArray of
+ *   #IdeDebuggerVariable if successful; otherwise %NULL and error is set.
+ *
+ * Since: 3.26
+ */
+GPtrArray *
+ide_debugger_list_params_finish (IdeDebugger   *self,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+  return IDE_DEBUGGER_GET_CLASS (self)->list_params_finish (self, result, error);
+}
+
+/**
+ * ide_debugger_list_registers_async:
+ * @self: an #IdeDebugger
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: A callback to call once the operation has finished
+ * @user_data: user data for @callback
+ *
+ * Requests the list of registers and their values.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_list_registers_async (IdeDebugger         *self,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEBUGGER_GET_CLASS (self)->list_registers_async (self, cancellable, callback, user_data);
+}
+
+/**
+ * ide_debugger_list_registers_finish:
+ * @self: a #IdeDebugger
+ * @result: A #GAsyncResult
+ * @error: a location for a #GError or %NULL
+ *
+ * Completes an asynchronous request to ide_debugger_list_registers_async().
+ *
+ * Returns: (transfer container) (element-type Ide.DebuggerRegister): A #GPtrArray of
+ *   #IdeDebuggerRegister if successful; otherwise %NULL and error is set.
+ *
+ * Since: 3.26
+ */
+GPtrArray *
+ide_debugger_list_registers_finish (IdeDebugger   *self,
+                                    GAsyncResult  *result,
+                                    GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+  return IDE_DEBUGGER_GET_CLASS (self)->list_registers_finish (self, result, error);
+}
+
+/**
+ * ide_debugger_disassemble_async:
+ * @self: an #IdeDebugger
+ * @range: an #IdeDebuggerAddressRange to disassemble
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: A callback to call once the operation has finished
+ * @user_data: user data for @callback
+ *
+ * Disassembles the address range requested.
+ *
+ * Since: 3.26
+ */
+void
+ide_debugger_disassemble_async (IdeDebugger                   *self,
+                                const IdeDebuggerAddressRange *range,
+                                GCancellable                  *cancellable,
+                                GAsyncReadyCallback            callback,
+                                gpointer                       user_data)
+{
+  g_return_if_fail (IDE_IS_DEBUGGER (self));
+  g_return_if_fail (range != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEBUGGER_GET_CLASS (self)->disassemble_async (self, range, cancellable, callback, user_data);
+}
+
+/**
+ * ide_debugger_disassemble_finish:
+ * @self: a #IdeDebugger
+ * @result: A #GAsyncResult
+ * @error: a location for a #GError or %NULL
+ *
+ * Completes an asynchronous request to ide_debugger_disassemble_async().
+ *
+ * Returns: (transfer container) (element-type Ide.DebuggerInstruction): A #GPtrArray
+ *   of #IdeDebuggerInstruction if successful; otherwise %NULL and error is set.
+ *
+ * Since: 3.26
+ */
+GPtrArray *
+ide_debugger_disassemble_finish (IdeDebugger   *self,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_DEBUGGER (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+  return IDE_DEBUGGER_GET_CLASS (self)->disassemble_finish (self, result, error);
+}
diff --git a/libide/debugger/ide-debugger.h b/libide/debugger/ide-debugger.h
new file mode 100644
index 0000000..c24fa1e
--- /dev/null
+++ b/libide/debugger/ide-debugger.h
@@ -0,0 +1,354 @@
+/* ide-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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+#include "ide-debugger-breakpoint.h"
+#include "ide-debugger-frame.h"
+#include "ide-debugger-instruction.h"
+#include "ide-debugger-library.h"
+#include "ide-debugger-register.h"
+#include "ide-debugger-thread-group.h"
+#include "ide-debugger-thread.h"
+#include "ide-debugger-types.h"
+#include "ide-debugger-variable.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DEBUGGER (ide_debugger_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeDebugger, ide_debugger, IDE, DEBUGGER, GObject)
+
+struct _IdeDebuggerClass
+{
+  GObjectClass parent_class;
+
+  /* Signals */
+
+  void       (*log)                      (IdeDebugger                  *self,
+                                          IdeDebuggerStream             stream,
+                                          GBytes                       *content);
+  void       (*thread_group_added)       (IdeDebugger                  *self,
+                                          IdeDebuggerThreadGroup       *thread_group);
+  void       (*thread_group_removed)     (IdeDebugger                  *self,
+                                          IdeDebuggerThreadGroup       *thread_group);
+  void       (*thread_group_started)     (IdeDebugger                  *self,
+                                          IdeDebuggerThreadGroup       *thread_group);
+  void       (*thread_group_exited)      (IdeDebugger                  *self,
+                                          IdeDebuggerThreadGroup       *thread_group);
+  void       (*thread_added)             (IdeDebugger                  *self,
+                                          IdeDebuggerThread            *thread);
+  void       (*thread_removed)           (IdeDebugger                  *self,
+                                          IdeDebuggerThread            *thread);
+  void       (*thread_selected)          (IdeDebugger                  *self,
+                                          IdeDebuggerThread            *thread);
+  void       (*breakpoint_added)         (IdeDebugger                  *self,
+                                          IdeDebuggerBreakpoint        *breakpoint);
+  void       (*breakpoint_removed)       (IdeDebugger                  *self,
+                                          IdeDebuggerBreakpoint        *breakpoint);
+  void       (*breakpoint_modified)      (IdeDebugger                  *self,
+                                          IdeDebuggerBreakpoint        *breakpoint);
+  void       (*running)                  (IdeDebugger                  *self);
+  void       (*stopped)                  (IdeDebugger                  *self,
+                                          IdeDebuggerStopReason         stop_reason,
+                                          IdeDebuggerBreakpoint        *breakpoint);
+  void       (*library_loaded)           (IdeDebugger                  *self,
+                                          IdeDebuggerLibrary           *library);
+  void       (*library_unloaded)         (IdeDebugger                  *self,
+                                          IdeDebuggerLibrary           *library);
+
+  /* Virtual Functions */
+
+  gboolean   (*get_can_move)             (IdeDebugger                  *self,
+                                          IdeDebuggerMovement           movement);
+  void       (*move_async)               (IdeDebugger                  *self,
+                                          IdeDebuggerMovement           movement,
+                                          GCancellable                 *cancellable,
+                                          GAsyncReadyCallback           callback,
+                                          gpointer                      user_data);
+  gboolean   (*move_finish)              (IdeDebugger                  *self,
+                                          GAsyncResult                 *result,
+                                          GError                      **error);
+  void       (*list_breakpoints_async)   (IdeDebugger                  *self,
+                                          GCancellable                 *cancellable,
+                                          GAsyncReadyCallback           callback,
+                                          gpointer                      user_data);
+  GPtrArray *(*list_breakpoints_finish)  (IdeDebugger                  *self,
+                                          GAsyncResult                 *result,
+                                          GError                      **error);
+  void       (*insert_breakpoint_async)  (IdeDebugger                  *self,
+                                          IdeDebuggerBreakpoint        *breakpoint,
+                                          GCancellable                 *cancellable,
+                                          GAsyncReadyCallback           callback,
+                                          gpointer                      user_data);
+  gboolean   (*insert_breakpoint_finish) (IdeDebugger                  *self,
+                                          GAsyncResult                 *result,
+                                          GError                      **error);
+  void       (*remove_breakpoint_async)  (IdeDebugger                  *self,
+                                          IdeDebuggerBreakpoint        *breakpoint,
+                                          GCancellable                 *cancellable,
+                                          GAsyncReadyCallback           callback,
+                                          gpointer                      user_data);
+  gboolean   (*remove_breakpoint_finish) (IdeDebugger                  *self,
+                                          GAsyncResult                 *result,
+                                          GError                      **error);
+  void       (*modify_breakpoint_async)  (IdeDebugger                  *self,
+                                          IdeDebuggerBreakpointChange   change,
+                                          IdeDebuggerBreakpoint        *breakpoint,
+                                          GCancellable                 *cancellable,
+                                          GAsyncReadyCallback           callback,
+                                          gpointer                      user_data);
+  gboolean   (*modify_breakpoint_finish) (IdeDebugger                  *self,
+                                          GAsyncResult                 *result,
+                                          GError                        **error);
+  void       (*list_frames_async)        (IdeDebugger                    *self,
+                                          IdeDebuggerThread              *thread,
+                                          GCancellable                   *cancellable,
+                                          GAsyncReadyCallback             callback,
+                                          gpointer                        user_data);
+  GPtrArray *(*list_frames_finish)       (IdeDebugger                    *self,
+                                          GAsyncResult                   *result,
+                                          GError                        **error);
+  void       (*interrupt_async)          (IdeDebugger                    *self,
+                                          IdeDebuggerThreadGroup         *thread_group,
+                                          GCancellable                   *cancellable,
+                                          GAsyncReadyCallback             callback,
+                                          gpointer                        user_data);
+  gboolean   (*interrupt_finish)         (IdeDebugger                    *self,
+                                          GAsyncResult                   *result,
+                                          GError                        **error);
+  void       (*send_signal_async)        (IdeDebugger                    *self,
+                                          gint                            signum,
+                                          GCancellable                   *cancellable,
+                                          GAsyncReadyCallback             callback,
+                                          gpointer                        user_data);
+  gboolean   (*send_signal_finish)       (IdeDebugger                    *self,
+                                          GAsyncResult                   *result,
+                                          GError                        **error);
+  void       (*list_locals_async)        (IdeDebugger                    *self,
+                                          IdeDebuggerThread              *thread,
+                                          IdeDebuggerFrame               *frame,
+                                          GCancellable                   *cancellable,
+                                          GAsyncReadyCallback             callback,
+                                          gpointer                        user_data);
+  GPtrArray *(*list_locals_finish)       (IdeDebugger                    *self,
+                                          GAsyncResult                   *result,
+                                          GError                        **error);
+  void       (*list_params_async)        (IdeDebugger                    *self,
+                                          IdeDebuggerThread              *thread,
+                                          IdeDebuggerFrame               *frame,
+                                          GCancellable                   *cancellable,
+                                          GAsyncReadyCallback             callback,
+                                          gpointer                        user_data);
+  GPtrArray *(*list_params_finish)       (IdeDebugger                    *self,
+                                          GAsyncResult                   *result,
+                                          GError                        **error);
+  void       (*list_registers_async)     (IdeDebugger                    *self,
+                                          GCancellable                   *cancellable,
+                                          GAsyncReadyCallback             callback,
+                                          gpointer                        user_data);
+  GPtrArray *(*list_registers_finish)    (IdeDebugger                    *self,
+                                          GAsyncResult                   *result,
+                                          GError                        **error);
+  void       (*disassemble_async)        (IdeDebugger                    *self,
+                                          const IdeDebuggerAddressRange  *range,
+                                          GCancellable                   *cancellable,
+                                          GAsyncReadyCallback             callback,
+                                          gpointer                        user_data);
+  GPtrArray *(*disassemble_finish)       (IdeDebugger                    *self,
+                                          GAsyncResult                   *result,
+                                          GError                        **error);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+  gpointer _reserved9;
+  gpointer _reserved10;
+  gpointer _reserved11;
+  gpointer _reserved12;
+  gpointer _reserved13;
+  gpointer _reserved14;
+  gpointer _reserved15;
+  gpointer _reserved16;
+  gpointer _reserved17;
+  gpointer _reserved18;
+  gpointer _reserved19;
+  gpointer _reserved20;
+  gpointer _reserved21;
+  gpointer _reserved22;
+  gpointer _reserved23;
+  gpointer _reserved24;
+  gpointer _reserved25;
+  gpointer _reserved26;
+  gpointer _reserved27;
+  gpointer _reserved28;
+  gpointer _reserved29;
+  gpointer _reserved30;
+  gpointer _reserved31;
+  gpointer _reserved32;
+};
+
+GListModel        *ide_debugger_get_breakpoints           (IdeDebugger                    *self);
+const gchar       *ide_debugger_get_display_name          (IdeDebugger                    *self);
+void               ide_debugger_set_display_name          (IdeDebugger                    *self,
+                                                           const gchar                    *display_name);
+gboolean           ide_debugger_get_is_running            (IdeDebugger                    *self);
+gboolean           ide_debugger_get_can_move              (IdeDebugger                    *self,
+                                                           IdeDebuggerMovement             movement);
+GListModel        *ide_debugger_get_threads               (IdeDebugger                    *self);
+GListModel        *ide_debugger_get_thread_groups         (IdeDebugger                    *self);
+IdeDebuggerThread *ide_debugger_get_selected_thread       (IdeDebugger                    *self);
+void               ide_debugger_disassemble_async         (IdeDebugger                    *self,
+                                                           const IdeDebuggerAddressRange  *range,
+                                                           GCancellable                   *cancellable,
+                                                           GAsyncReadyCallback             callback,
+                                                           gpointer                        user_data);
+GPtrArray         *ide_debugger_disassemble_finish        (IdeDebugger                    *self,
+                                                           GAsyncResult                   *result,
+                                                           GError                        **error);
+void               ide_debugger_insert_breakpoint_async   (IdeDebugger                    *self,
+                                                           IdeDebuggerBreakpoint          *breakpoint,
+                                                           GCancellable                   *cancellable,
+                                                           GAsyncReadyCallback             callback,
+                                                           gpointer                        user_data);
+gboolean           ide_debugger_insert_breakpoint_finish  (IdeDebugger                    *self,
+                                                           GAsyncResult                   *result,
+                                                           GError                        **error);
+void               ide_debugger_interrupt_async           (IdeDebugger                    *self,
+                                                           IdeDebuggerThreadGroup         *thread_group,
+                                                           GCancellable                   *cancellable,
+                                                           GAsyncReadyCallback             callback,
+                                                           gpointer                        user_data);
+gboolean           ide_debugger_interrupt_finish          (IdeDebugger                    *self,
+                                                           GAsyncResult                   *result,
+                                                           GError                        **error);
+void               ide_debugger_modify_breakpoint_async   (IdeDebugger                    *self,
+                                                           IdeDebuggerBreakpointChange     change,
+                                                           IdeDebuggerBreakpoint          *breakpoint,
+                                                           GCancellable                   *cancellable,
+                                                           GAsyncReadyCallback             callback,
+                                                           gpointer                        user_data);
+gboolean           ide_debugger_modify_breakpoint_finish  (IdeDebugger                    *self,
+                                                           GAsyncResult                   *result,
+                                                           GError                        **error);
+void               ide_debugger_remove_breakpoint_async   (IdeDebugger                    *self,
+                                                           IdeDebuggerBreakpoint          *breakpoint,
+                                                           GCancellable                   *cancellable,
+                                                           GAsyncReadyCallback             callback,
+                                                           gpointer                        user_data);
+gboolean           ide_debugger_remove_breakpoint_finish  (IdeDebugger                    *self,
+                                                           GAsyncResult                   *result,
+                                                           GError                        **error);
+void               ide_debugger_list_breakpoints_async    (IdeDebugger                    *self,
+                                                           GCancellable                   *cancellable,
+                                                           GAsyncReadyCallback             callback,
+                                                           gpointer                        user_data);
+GPtrArray         *ide_debugger_list_breakpoints_finish   (IdeDebugger                    *self,
+                                                           GAsyncResult                   *result,
+                                                           GError                        **error);
+void               ide_debugger_list_frames_async         (IdeDebugger                    *self,
+                                                           IdeDebuggerThread              *thread,
+                                                           GCancellable                   *cancellable,
+                                                           GAsyncReadyCallback             callback,
+                                                           gpointer                        user_data);
+GPtrArray         *ide_debugger_list_frames_finish        (IdeDebugger                    *self,
+                                                           GAsyncResult                   *result,
+                                                           GError                        **error);
+void               ide_debugger_list_locals_async         (IdeDebugger                    *self,
+                                                           IdeDebuggerThread              *thread,
+                                                           IdeDebuggerFrame               *frame,
+                                                           GCancellable                   *cancellable,
+                                                           GAsyncReadyCallback             callback,
+                                                           gpointer                        user_data);
+GPtrArray         *ide_debugger_list_locals_finish        (IdeDebugger                    *self,
+                                                           GAsyncResult                   *result,
+                                                           GError                        **error);
+void               ide_debugger_list_params_async         (IdeDebugger                    *self,
+                                                           IdeDebuggerThread              *thread,
+                                                           IdeDebuggerFrame               *frame,
+                                                           GCancellable                   *cancellable,
+                                                           GAsyncReadyCallback             callback,
+                                                           gpointer                        user_data);
+GPtrArray         *ide_debugger_list_params_finish        (IdeDebugger                    *self,
+                                                           GAsyncResult                   *result,
+                                                           GError                        **error);
+void               ide_debugger_list_registers_async      (IdeDebugger                    *self,
+                                                           GCancellable                   *cancellable,
+                                                           GAsyncReadyCallback             callback,
+                                                           gpointer                        user_data);
+GPtrArray         *ide_debugger_list_registers_finish     (IdeDebugger                    *self,
+                                                           GAsyncResult                   *result,
+                                                           GError                        **error);
+void               ide_debugger_move_async                (IdeDebugger                    *self,
+                                                           IdeDebuggerMovement             movement,
+                                                           GCancellable                   *cancellable,
+                                                           GAsyncReadyCallback             callback,
+                                                           gpointer                        user_data);
+gboolean           ide_debugger_move_finish               (IdeDebugger                    *self,
+                                                           GAsyncResult                   *result,
+                                                           GError                        **error);
+void               ide_debugger_send_signal_async         (IdeDebugger                    *self,
+                                                           gint                            signum,
+                                                           GCancellable                   *cancellable,
+                                                           GAsyncReadyCallback             callback,
+                                                           gpointer                        user_data);
+gboolean           ide_debugger_send_signal_finish        (IdeDebugger                    *self,
+                                                           GAsyncResult                   *result,
+                                                           GError                        **error);
+const gchar       *ide_debugger_locate_binary_at_address  (IdeDebugger                    *self,
+                                                           IdeDebuggerAddress              address);
+void               ide_debugger_emit_log                  (IdeDebugger                    *self,
+                                                           IdeDebuggerStream               stream,
+                                                           GBytes                         *content);
+void               ide_debugger_emit_thread_group_added   (IdeDebugger                    *self,
+                                                           IdeDebuggerThreadGroup         *thread_group);
+void               ide_debugger_emit_thread_group_removed (IdeDebugger                    *self,
+                                                           IdeDebuggerThreadGroup         *thread_group);
+void               ide_debugger_emit_thread_group_started (IdeDebugger                    *self,
+                                                           IdeDebuggerThreadGroup         *thread_group);
+void               ide_debugger_emit_thread_group_exited  (IdeDebugger                    *self,
+                                                           IdeDebuggerThreadGroup         *thread_group);
+void               ide_debugger_emit_thread_added         (IdeDebugger                    *self,
+                                                           IdeDebuggerThread              *thread);
+void               ide_debugger_emit_thread_removed       (IdeDebugger                    *self,
+                                                           IdeDebuggerThread              *thread);
+void               ide_debugger_emit_thread_selected      (IdeDebugger                    *self,
+                                                           IdeDebuggerThread              *thread);
+void               ide_debugger_emit_breakpoint_added     (IdeDebugger                    *self,
+                                                           IdeDebuggerBreakpoint          *breakpoint);
+void               ide_debugger_emit_breakpoint_modified  (IdeDebugger                    *self,
+                                                           IdeDebuggerBreakpoint          *breakpoint);
+void               ide_debugger_emit_breakpoint_removed   (IdeDebugger                    *self,
+                                                           IdeDebuggerBreakpoint          *breakpoint);
+void               ide_debugger_emit_running              (IdeDebugger                    *self);
+void               ide_debugger_emit_stopped              (IdeDebugger                    *self,
+                                                           IdeDebuggerStopReason           stop_reason,
+                                                           IdeDebuggerBreakpoint          *breakpoint);
+void               ide_debugger_emit_library_loaded       (IdeDebugger                    *self,
+                                                           IdeDebuggerLibrary             *library);
+void               ide_debugger_emit_library_unloaded     (IdeDebugger                    *self,
+                                                           IdeDebuggerLibrary             *library);
+
+G_END_DECLS
diff --git a/libide/ide.h b/libide/ide.h
index 8d2d512..0cd7a51 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -62,6 +62,16 @@ G_BEGIN_DECLS
 #include "buildsystem/ide-configuration-provider.h"
 #include "buildsystem/ide-environment-variable.h"
 #include "buildsystem/ide-environment.h"
+#include "debugger/ide-debugger-breakpoint.h"
+#include "debugger/ide-debugger-frame.h"
+#include "debugger/ide-debugger-instruction.h"
+#include "debugger/ide-debugger-library.h"
+#include "debugger/ide-debugger-register.h"
+#include "debugger/ide-debugger-thread-group.h"
+#include "debugger/ide-debugger-thread.h"
+#include "debugger/ide-debugger-types.h"
+#include "debugger/ide-debugger-variable.h"
+#include "debugger/ide-debugger.h"
 #include "devices/ide-device-manager.h"
 #include "devices/ide-device-provider.h"
 #include "devices/ide-device.h"
diff --git a/libide/meson.build b/libide/meson.build
index 58f1b1f..59bdc63 100644
--- a/libide/meson.build
+++ b/libide/meson.build
@@ -85,6 +85,16 @@ libide_public_headers = [
   'buildsystem/ide-configuration-provider.h',
   'buildsystem/ide-environment-variable.h',
   'buildsystem/ide-environment.h',
+  'debugger/ide-debugger-breakpoint.h',
+  'debugger/ide-debugger-frame.h',
+  'debugger/ide-debugger-instruction.h',
+  'debugger/ide-debugger-library.h',
+  'debugger/ide-debugger-register.h',
+  'debugger/ide-debugger-thread-group.h',
+  'debugger/ide-debugger-thread.h',
+  'debugger/ide-debugger-types.h',
+  'debugger/ide-debugger-variable.h',
+  'debugger/ide-debugger.h',
   'devices/ide-device-manager.h',
   'devices/ide-device-provider.h',
   'devices/ide-device.h',
@@ -286,6 +296,16 @@ libide_public_sources = [
   'buildsystem/ide-configuration-provider.c',
   'buildsystem/ide-environment-variable.c',
   'buildsystem/ide-environment.c',
+  'debugger/ide-debugger-breakpoint.c',
+  'debugger/ide-debugger-frame.c',
+  'debugger/ide-debugger-instruction.c',
+  'debugger/ide-debugger-library.c',
+  'debugger/ide-debugger-register.c',
+  'debugger/ide-debugger-thread-group.c',
+  'debugger/ide-debugger-thread.c',
+  'debugger/ide-debugger-types.c',
+  'debugger/ide-debugger-variable.c',
+  'debugger/ide-debugger.c',
   'devices/ide-device-manager.c',
   'devices/ide-device-provider.c',
   'devices/ide-device.c',
@@ -480,6 +500,21 @@ libide_sources = libide_generated_headers + libide_public_sources + [
   'buildui/ide-environment-editor-row.h',
   'buildui/ide-environment-editor.c',
   'buildui/ide-environment-editor.h',
+  'debugger/ide-debugger-actions.c',
+  'debugger/ide-debugger-address-map.c',
+  'debugger/ide-debugger-address-map.h',
+  'debugger/ide-debugger-breakpoints-view.c',
+  'debugger/ide-debugger-breakpoints-view.h',
+  'debugger/ide-debugger-fallbacks.c',
+  'debugger/ide-debugger-libraries-view.c',
+  'debugger/ide-debugger-libraries-view.h',
+  'debugger/ide-debugger-locals-view.c',
+  'debugger/ide-debugger-locals-view.h',
+  'debugger/ide-debugger-private.h',
+  'debugger/ide-debugger-registers-view.c',
+  'debugger/ide-debugger-registers-view.h',
+  'debugger/ide-debugger-threads-view.c',
+  'debugger/ide-debugger-threads-view.h',
   'editor/ide-editor-layout-stack-addin.c',
   'editor/ide-editor-layout-stack-addin.h',
   'editor/ide-editor-layout-stack-controls.c',


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