Make --gdb work again by using gdbserver

--gdb now starts gdbserver and I've added a new
command gdb_attach which knows how to launch gdb
and connect to the launched gdbserver

This doesn't quite work on android yet, but it's very close.

R=abarth@chromium.org, qsr@chromium.org
BUG=

Review URL: https://codereview.chromium.org/808053006
diff --git a/sky/tools/skydb b/sky/tools/skydb
index 191d6d1..6f06364 100755
--- a/sky/tools/skydb
+++ b/sky/tools/skydb
@@ -31,6 +31,7 @@
 ]
 
 DEFAULT_SKY_COMMAND_PORT = 7777
+GDB_PORT = 8888
 SKY_SERVER_PORT = 9999
 PID_FILE_PATH = "/tmp/skydb.pids"
 DEFAULT_URL = "https://raw.githubusercontent.com/domokit/mojo/master/sky/examples/home.sky"
@@ -132,12 +133,15 @@
             configuration, server_root)
         return sky_server
 
-    def start_command(self, args):
+    def _create_paths_for_build_dir(self, build_dir):
         # skypy.paths.Paths takes a root-relative build_dir argument. :(
-        build_dir = os.path.abspath(args.build_dir)
-        root_relative_build_dir = os.path.relpath(build_dir, SRC_ROOT)
+        abs_build_dir = os.path.abspath(build_dir)
+        root_relative_build_dir = os.path.relpath(abs_build_dir, SRC_ROOT)
+        return skypy.paths.Paths(root_relative_build_dir)
+
+    def start_command(self, args):
         # FIXME: Lame that we use self for a command-specific variable.
-        self.paths = skypy.paths.Paths(root_relative_build_dir)
+        self.paths = self._create_paths_for_build_dir(args.build_dir)
 
         self.stop_command(None) # Quit any existing process.
         self.pids = {} # Clear out our pid file.
@@ -174,20 +178,32 @@
             self.pids['remote_sky_command_port'] = args.command_port
 
         shell_command = self._build_mojo_shell_command(args)
+
+        # On android we can't launch inside gdb, but rather have to attach.
+        if args.gdb and not is_android:
+            shell_command = ['gdbserver', ':%s' % GDB_PORT] + shell_command
+
         print ' '.join(map(pipes.quote, shell_command))
         self.pids['mojo_shell_pid'] = subprocess.Popen(shell_command).pid
 
-        if args.gdb:
-            print "Sorry, I'm not sure how best to wire up --gdb to work"
-            print "with mojo_shell as a background process.  For now use:"
-            print "gdb --pid %s" % self.pids['mojo_shell_pid']
-            shell_command = ['gdb'] + shell_command
+        if args.gdb and is_android:
+            gdbserver_cmd = ['gdbserver', '--attach', ':%s' % GDB_PORT]
+            self.pids['remote_gdbserver_pid'] = subprocess.Popen(shell_command).pid
 
-        if not self._wait_for_sky_command_port():
-            logging.error('Failed to start sky')
-            self.stop_command(None)
+            port_string = 'tcp:%s' % GDB_PORT
+            subprocess.check_call([
+                'adb', 'forward', port_string, port_string
+            ])
+            self.pids['remote_gdbserver_port'] = GDB_PORT
+
+        if not args.gdb:
+            if not self._wait_for_sky_command_port():
+                logging.error('Failed to start sky')
+                self.stop_command(None)
+            else:
+                self.load_command(args)
         else:
-            self.load_command(args)
+            print 'No load issued, connect with gdb first and then run load.'
 
     def _kill_if_exists(self, key, name):
         pid = self.pids.pop(key, None)
@@ -207,7 +223,7 @@
 
         self._kill_if_exists('sky_server_pid', 'sky_server')
         # We could be much more surgical here:
-        if 'remote_sky_command_port' in self.pids:
+        if 'remote_sky_server_port' in self.pids:
             device = android_commands.AndroidCommands(
                 self.pids['device_serial'])
             forwarder.Forwarder.UnmapAllDevicePorts(device)
@@ -217,6 +233,10 @@
             port_string = 'tcp:%s' % self.pids['sky_command_port']
             subprocess.call(['adb', 'forward', '--remove', port_string])
 
+        if 'remote_gdbserver_port' in self.pids:
+            port_string = 'tcp:%s' % self.pids['remote_gdbserver_port']
+            subprocess.call(['adb', 'forward', '--remove', port_string])
+
     def load_command(self, args):
         if not urlparse.urlparse(args.url_or_path).scheme:
             # The load happens on the remote device, use the remote port.
@@ -287,6 +307,17 @@
         ]
         subprocess.call(['adb', 'logcat', '-s'] + TAGS)
 
+    def gdb_attach_command(self, args):
+        self.paths = self._create_paths_for_build_dir(self.pids['build_dir'])
+        gdb_command = [
+            '/usr/bin/gdb', self.paths.mojo_shell_path,
+            '--eval-command', 'target remote localhost:%s' % GDB_PORT
+        ]
+        print " ".join(gdb_command)
+        # We don't want python listenting for signals or anything, so exec
+        # gdb and let it take the entire process.
+        os.execv(gdb_command[0], gdb_command)
+
     def main(self):
         logging.basicConfig(level=logging.INFO)
         logging.getLogger("requests").setLevel(logging.WARNING)
@@ -318,6 +349,10 @@
             help=('dump sky-related logs from device'))
         logcat_parser.set_defaults(func=self.logcat_command)
 
+        gdb_attach_parser = subparsers.add_parser('gdb_attach',
+            help='launch gdb and attach to gdbserver launched from start --gdb')
+        gdb_attach_parser.set_defaults(func=self.gdb_attach_command)
+
         self._add_basic_command(subparsers, 'trace', '/trace',
             'toggle tracing')
         self._add_basic_command(subparsers, 'reload', '/reload',