diff --git a/doc/man/ade-debug.1 b/doc/man/ade-debug.1
new file mode 100644
index 0000000000000000000000000000000000000000..0a425b2d8c1d6d445d970e213bfbd68a014532d1
--- /dev/null
+++ b/doc/man/ade-debug.1
@@ -0,0 +1,45 @@
+.TH ADE\-DEBUG 1 14/12/2016 0.1612.0 Apertis\ Development\ Tools\ Manual
+
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+
+.SH NAME
+
+ade-debug \- Start an application in debug mode
+
+.SH SYNOPSIS
+.sp
+.nf
+\fIade debug\fR --app=<app> --device [<user>[:<pass>]@]<hostname>[:<port>] \fBARGS\fR
+.fi
+.sp
+.SH DESCRIPTION
+.sp
+Start an application on given device in debug mode and connect GDB to it\&.
+The tool should be run with the target project directory as the current one\&.
+Ideally, the project should have been configured with the \-\-debug option so
+the debug symbols are available\&.
+.sp
+.SH OPTIONS
+.sp
+.PP
+\fI\-\-app\fR
+.RS 4
+Absolute path to application to start\&.
+.sp
+.RE
+.PP
+\fI\-\-device\fR
+.RS 4
+Use device as target (e.g. user@apertis)\&.
+.sp
+.RE
+.SH SEE ALSO
+
+ade(1)
+
+.SH COPYRIGHT
+
+Copyright (c) 2016 Collabora Ltd.
diff --git a/doc/man/ade.1 b/doc/man/ade.1
index 95450c3ec518677dfa89d412881edb2a6cb2d217..ddf297552dbb8902dc242be591e18c1911ad560e 100644
--- a/doc/man/ade.1
+++ b/doc/man/ade.1
@@ -101,6 +101,14 @@ Start application in simulator or on device\&.
 See \fBade-run(1)\fR
 .sp
 .RE
+.PP
+\fIdebug\fR
+.RS 4
+Start application in debug mode\&.
+.sp
+See \fBade-debug(1)\fR
+.sp
+.RE
 .SH SEE ALSO
 
 ade-sysroot(1)
diff --git a/tools/ade b/tools/ade
index 1fd24917821a653fcb0ab3ec49289332cf1e9250..8afb9963a89cc4c720291cc3848546bf13385e4d 100755
--- a/tools/ade
+++ b/tools/ade
@@ -27,6 +27,8 @@ import subprocess
 import sys
 import tarfile
 import tempfile
+import time
+import threading
 import urllib
 import xdg.BaseDirectory
 
@@ -37,6 +39,7 @@ from urllib.parse import urlparse
 from urllib.request import urlopen, urlretrieve
 
 HARD_FLOAT_FLAG = 0x00000400
+DEFAULT_GDBSERVER_PORT = 1234
 
 
 def print_progress(count, blockSize, total):
@@ -171,6 +174,72 @@ class TargetTriplet:
         raise NotSupportedError
 
 
+class DebuggerServerThread(threading.Thread):
+
+    def __init__(self, target, port, app, *args):
+        super().__init__()
+        self.target = target
+        self.port = port
+        self.app = app
+        self.args = args
+
+    def run(self):
+        self._data = self.target.start_gdbserver(self.port, self.app, *self.args)
+
+    def stop(self):
+        self.target.stop_gdbserver(self._data)
+
+
+class DebuggerServer:
+
+    def __init__(self, target, app, *args):
+        self.target = target
+        self.app = app
+        self.args = args
+        self.port = DEFAULT_GDBSERVER_PORT
+        self._thread = None
+
+    def __enter__(self):
+        self._thread = DebuggerServerThread(self.target, self.port, self.app, *self.args)
+        self._thread.start()
+        return self
+
+    def __exit__(self, et, ev, tb):
+        self._thread.stop()
+        self._thread.join()
+
+    def get_info(self):
+        return "tcp:{}:{}".format(self.target.host, self.port)
+
+
+class Debugger:
+
+    def __init__(self, target, project):
+        self.target = target
+        self.project = project
+
+    def _get_commands(self, server, debugdir, libdir):
+        cmds = []
+        if isinstance(self.target, Sysroot):
+            cmds.append("set sysroot {}".format(self.target.path))
+        cmds.append("set solib-search-path {}".format(libdir))
+        cmds.append("file {}{}".format(debugdir, server.app))
+        cmds.append("target remote {}".format(server.get_info()))
+        return cmds
+
+    def connect(self, server):
+        debugdir = os.path.join(self.project.root, 'debug')
+        libdir = os.path.join(self.project.root, 'debug', 'Applications', self.project.bundle_id, 'lib')
+        self.project.install(debugdir)
+
+        cmds = self._get_commands(server, debugdir, libdir)
+        args = ['gdb']
+        for cmd in cmds:
+            args.append('-ex')
+            args.append(cmd)
+        os.execv('/usr/bin/gdb', args)
+
+
 class Simulator:
 
     def __init__(self):
@@ -249,6 +318,11 @@ class Device:
 
             return out
 
+    def _run(self, *args):
+        ssh = self._connect()
+        stdin, stdout, stderr = ssh.exec_command(' '.join(args))
+        return ssh, stdout.channel
+
     def load_sysroot_version(self):
         try:
             v = self._exec('cat', '/etc/image_version')
@@ -280,6 +354,29 @@ class Device:
     def run(self, app, *args):
         self._exec('canterbury-exec', app, *args)
 
+    def _wait_gdbserver(self, channel):
+        data = b''
+        while True:
+            if channel.exit_status_ready():
+                status = channel.recv_exit_status()
+                if status != 0:
+                    raise CommandFailedError('', data.decode().strip(), status)
+            while channel.recv_stderr_ready():
+                data += channel.recv_stderr(1024)
+                if 'Listening on port' in data.decode():
+                    return
+            time.sleep(1)
+
+    def start_gdbserver(self, port, app, *args):
+        host = self.host
+        conn = "{}:{}".format(host, port)
+        ssh, chan = self._run('gdbserver', '--wrapper', 'canterbury-exec', '--', conn, app, *args)
+        self._wait_gdbserver(chan)
+        return (ssh, chan)
+
+    def stop_gdbserver(self, data):
+        pass
+
 
 class SysrootVersion:
 
@@ -1263,6 +1360,14 @@ class Ade:
         target = self.get_target()
         target.run(self.app, *self.args)
 
+    def do_debug(self):
+        target = self.get_target()
+        project = Project()
+
+        with DebuggerServer(target, self.app, *self.args) as server:
+            gdb = Debugger(self.unpack_sysroot(target), project)
+            gdb.connect(server)
+
     def info(self, message):
         if self.format == 'friendly':
             print(message)
@@ -1379,6 +1484,13 @@ if __name__ == '__main__':
     group.add_argument('--device', help="Use device as target (e.g. user@apertis)")
     run_parser.add_argument('args', help="Arguments to pass to application", nargs=argparse.REMAINDER)
 
+    # Debug parser
+    debug_parser = subparsers.add_parser('debug', help="Debug application")
+    debug_parser.add_argument('--app', help="Remote path to application to debug")
+    group = debug_parser.add_mutually_exclusive_group()
+    group.add_argument('--device', help="Use device as target (e.g. user@apertis)")
+    debug_parser.add_argument('args', help="Arguments to pass to application", nargs=argparse.REMAINDER)
+
     argcomplete.autocomplete(root_parser)
 
     obj = Ade()