1#
2# Copyright (C) 2012 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17#
18# GDB plugin to allow debugging of apps on remote Android systems using gdbserver.
19#
20# To use this plugin, source this file from a Python-enabled GDB client, then use:
21#   load-android-app <app-source-dir>  to tell GDB about the app you are debugging
22#   run-android-app to start the app in a running state
23#   start-android-app to start the app in a paused state
24#   attach-android-ap to attach to an existing (running) instance of app
25#   set-android-device to select a target (only if multiple devices are attached)
26
27import fnmatch
28import gdb
29import os
30import shutil
31import subprocess
32import tempfile
33import time
34
35be_verbose = False
36enable_renderscript_dumps = True
37local_symbols_library_directory = os.path.join(os.getenv('ANDROID_PRODUCT_OUT', 'out'),
38      'symbols', 'system', 'lib')
39local_library_directory = os.path.join(os.getenv('ANDROID_PRODUCT_OUT', 'out'),
40      'system', 'lib')
41
42# ADB              - Basic ADB wrapper, far from complete
43# DebugAppInfo     - App configuration struct, as far as GDB cares
44# StartAndroidApp  - Implementation of GDB start (for android apps)
45# RunAndroidApp    - Implementation of GDB run (for android apps)
46# AttachAndroidApp - GDB command to attach to an existing android app process
47# AndroidStatus    - app status query command (not needed, mostly harmless)
48# LoadAndroidApp   - Sets the package and intent names for an app
49
50def _interesting_libs():
51  return ['libc', 'libbcc', 'libRS', 'libandroid_runtime', 'libart']
52
53# In python 2.6, subprocess.check_output does not exist, so it is implemented here
54def check_output(*popenargs, **kwargs):
55  p = subprocess.Popen(stdout=subprocess.PIPE, stderr=subprocess.STDOUT, *popenargs, **kwargs)
56  out, err = p.communicate()
57  retcode = p.poll()
58  if retcode != 0:
59    c = kwargs.get("args")
60    if c is None:
61      c = popenargs[0]
62    e = subprocess.CalledProcessError(retcode, c)
63    e.output = str(out) + str(err)
64    raise e
65  return out
66
67class DebugAppInfo:
68  """Stores information from an app manifest"""
69
70  def __init__(self):
71    self.name = None
72    self.intent = None
73
74  def get_name(self):
75    return self.name
76
77  def get_intent(self):
78    return self.intent
79
80  def get_data_directory(self):
81    return self.data_directory
82
83  def get_gdbserver_path(self):
84    return os.path.join(self.data_directory, "lib", "gdbserver")
85
86  def set_info(self, name, intent, data_directory):
87    self.name = name
88    self.intent = intent
89    self.data_directory = data_directory
90
91  def unset_info():
92    self.name = None
93    self.intent = None
94    self.data_directory = None
95
96class ADB:
97  """
98  Python class implementing a basic ADB wrapper for useful commands.
99  Uses subprocess to invoke adb.
100  """
101
102  def __init__(self, device=None, verbose=False):
103    self.verbose = verbose
104    self.current_device = device
105    self.temp_libdir = None
106    self.background_processes = []
107    self.android_build_top = os.getenv('ANDROID_BUILD_TOP', None)
108    if not self.android_build_top:
109      raise gdb.GdbError("Unable to read ANDROID_BUILD_TOP. " \
110        + "Is your environment setup correct?")
111
112    self.adb_path = os.path.join(self.android_build_top,
113                      'out', 'host', 'linux-x86', 'bin', 'adb')
114
115    if not self.current_device:
116      devices = self.devices()
117      if len(devices) == 1:
118        self.set_current_device(devices[0])
119        return
120      else:
121        msg = ""
122        if len(devices) == 0:
123          msg = "No devices detected. Please connect a device and "
124        else:
125          msg = "Too many devices (" + ", ".join(devices) + ") detected. " \
126              + "Please "
127
128        print "Warning: " + msg + " use the set-android-device command."
129
130
131  def _prepare_adb_args(self, args):
132    largs = list(args)
133
134    # Prepare serial number option from current_device
135    if self.current_device and len(self.current_device) > 0:
136      largs.insert(0, self.current_device)
137      largs.insert(0, "-s")
138
139    largs.insert(0, self.adb_path)
140    return largs
141
142
143  def _background_adb(self, *args):
144    largs = self._prepare_adb_args(args)
145    p = None
146    try:
147      if self.verbose:
148        print "### " + str(largs)
149      p = subprocess.Popen(largs)
150      self.background_processes.append(p)
151    except CalledProcessError, e:
152      raise gdb.GdbError("Error starting background adb " + str(largs))
153    except:
154      raise gdb.GdbError("Unknown error starting background adb " + str(largs))
155
156    return p
157
158  def _call_adb(self, *args):
159    output = ""
160    largs = self._prepare_adb_args(args)
161    try:
162      if self.verbose:
163        print "### " + str(largs)
164      output = check_output(largs)
165    except subprocess.CalledProcessError, e:
166      raise gdb.GdbError("Error starting adb " + str(largs))
167    except Exception as e:
168      raise gdb.GdbError("Unknown error starting adb " + str(largs))
169
170    return output
171
172  def _shell(self, *args):
173    args = ["shell"] + list(args)
174    return self._call_adb(*args)
175
176  def _background_shell(self, *args):
177    args = ["shell"] + list(args)
178    return self._background_adb(*args)
179
180  def _cleanup_background_processes(self):
181    for handle in self.background_processes:
182      try:
183        handle.terminate()
184      except OSError, e:
185        # Background process died already
186        pass
187
188  def _cleanup_temp(self):
189    if self.temp_libdir:
190      shutil.rmtree(self.temp_libdir)
191      self.temp_libdir = None
192
193  def __del__(self):
194    self._cleanup_temp()
195    self._cleanup_background_processes()
196
197  def _get_local_libs(self):
198    ret = []
199    for lib in _interesting_libs():
200      lib_path = os.path.join(local_library_directory, lib + ".so")
201      if not os.path.exists(lib_path) and self.verbose:
202        print "Warning: unable to find expected library " \
203          + lib_path + "."
204      ret.append(lib_path)
205
206    return ret
207
208  def _check_remote_libs_match_local_libs(self):
209    ret = []
210    all_remote_libs = self._shell("ls", "/system/lib/*.so").split()
211    local_libs = self._get_local_libs()
212
213    self.temp_libdir = tempfile.mkdtemp()
214
215    for lib in _interesting_libs():
216      lib += ".so"
217      for remote_lib in all_remote_libs:
218        if lib in remote_lib:
219          # Pull lib from device and compute hash
220          tmp_path = os.path.join(self.temp_libdir, lib)
221          self.pull(remote_lib, tmp_path)
222          remote_hash = self._md5sum(tmp_path)
223
224          # Find local lib and compute hash
225          built_library = filter(lambda l: lib in l, local_libs)[0]
226          built_hash = self._md5sum(built_library)
227
228          # Alert user if library mismatch is detected
229          if built_hash != remote_hash:
230            self._cleanup_temp()
231            raise gdb.GdbError("Library mismatch between:\n" \
232              + "\t(" + remote_hash + ") " + tmp_path + " (from target) and\n " \
233              + "\t(" + built_hash + ") " + built_library + " (on host)\n" \
234              + "The target is running a different build than the host." \
235              + " This situation is not debuggable.")
236
237    self._cleanup_temp()
238
239  def _md5sum(self, file):
240    try:
241      return check_output(["md5sum", file]).strip().split()[0]
242    except subprocess.CalledProcessError, e:
243      raise gdb.GdbError("Error invoking md5sum commandline utility")
244
245  # Returns the list of serial numbers of connected devices
246  def devices(self):
247    ret = []
248    raw_output = self._call_adb("devices").split()
249    if len(raw_output) < 5:
250      return None
251    else:
252      for serial_num_index in range(4, len(raw_output), 2):
253        ret.append(raw_output[serial_num_index])
254    return ret
255
256  def set_current_device(self, serial):
257    if self.current_device == str(serial):
258      print "Current device already is: " + str(serial)
259      return
260
261    # TODO: this function should probably check the serial is valid.
262    self.current_device = str(serial)
263
264    api_version = self.getprop("ro.build.version.sdk")
265    if api_version < 15:
266      print "Warning: untested API version. Upgrade to 15 or higher"
267
268    # Verify the local libraries loaded by GDB are identical to those
269    # sitting on the device actually executing. Alert the user if
270    # this is happening
271    self._check_remote_libs_match_local_libs()
272
273  # adb getprop [property]
274  # if property is not None, returns the given property, otherwise
275  # returns all properties.
276  def getprop(self, property=None):
277    if property == None:
278      # get all the props
279      return self._call_adb(*["shell", "getprop"]).split('\n')
280    else:
281      return str(self._call_adb(*["shell", "getprop",
282        str(property)]).split('\n')[0])
283
284  # adb push
285  def push(self, source, destination):
286    self._call_adb(*["push", source, destination])
287
288  # adb forward <source> <destination>
289  def forward(self, source, destination):
290    self._call_adb(*["forward", source, destination])
291
292  # Returns true if filename exists on Android fs, false otherwise
293  def exists(self, filename):
294    raw_listing = self._shell(*["ls", filename])
295    return "No such file or directory" not in raw_listing
296
297  # adb pull <remote_path> <local_path>
298  def pull(self, remote_path, local_path):
299    self._call_adb(*["pull", remote_path, local_path])
300
301  #wrapper for adb shell ps. leave process_name=None for list of all processes
302  #Otherwise, returns triple with process name, pid and owner,
303  def get_process_info(self, process_name=None):
304    ret = []
305    raw_output = self._shell("ps")
306    for raw_line in raw_output.splitlines()[1:]:
307      line = raw_line.split()
308      name = line[-1]
309
310      if process_name == None or name == process_name:
311        user = line[0]
312        pid = line[1]
313
314        if process_name != None:
315          return (pid, user)
316        else:
317          ret.append((pid, user))
318
319    # No match in target process
320    if process_name != None:
321      return (None, None)
322
323    return ret
324
325  def kill_by_pid(self, pid):
326    self._shell(*["kill", "-9", pid])
327
328  def kill_by_name(self, process_name):
329    (pid, user) = self.get_process_info(process_name)
330    while pid != None:
331      self.kill_by_pid(pid)
332      (pid, user) = self.get_process_info(process_name)
333
334class AndroidStatus(gdb.Command):
335  """Implements the android-status gdb command."""
336
337  def __init__(self, adb, name="android-status", cat=gdb.COMMAND_OBSCURE, verbose=False):
338    super (AndroidStatus, self).__init__(name, cat)
339    self.verbose = verbose
340    self.adb = adb
341
342  def _update_status(self, process_name, gdbserver_process_name):
343    self._check_app_is_loaded()
344
345    # Update app status
346    (self.pid, self.owner_user) = \
347      self.adb.get_process_info(process_name)
348    self.running = self.pid != None
349
350    # Update gdbserver status
351    (self.gdbserver_pid, self.gdbserver_user) = \
352      self.adb.get_process_info(gdbserver_process_name)
353    self.gdbserver_running = self.gdbserver_pid != None
354
355    # Print results
356    if self.verbose:
357      print "--==Android GDB Plugin Status Update==--"
358      print "\tinferior name: " + process_name
359      print "\trunning: " + str(self.running)
360      print "\tpid: " + str(self.pid)
361      print "\tgdbserver running: " + str(self.gdbserver_running)
362      print "\tgdbserver pid: " + str(self.gdbserver_pid)
363      print "\tgdbserver user: " + str(self.gdbserver_user)
364
365  def _check_app_is_loaded(self):
366    if not currentAppInfo.get_name():
367      raise gdb.GdbError("Error: no app loaded. Try load-android-app.")
368
369  def invoke(self, arg, from_tty):
370    self._check_app_is_loaded()
371    self._update_status(currentAppInfo.get_name(),
372      currentAppInfo.get_gdbserver_path())
373    # TODO: maybe print something if verbose is off
374
375class StartAndroidApp (AndroidStatus):
376  """Implements the 'start-android-app' gdb command."""
377
378  def _update_status(self):
379    AndroidStatus._update_status(self, self.process_name, \
380      self.gdbserver_path)
381
382  # Calls adb shell ps every retry_delay seconds and returns
383  # the pid when process_name show up in output, or return 0
384  # after num_retries attempts. num_retries=0 means retry
385  # indefinitely.
386  def _wait_for_process(self, process_name, retry_delay=1, num_retries=10):
387    """ This function is a hack and should not be required"""
388    (pid, user) = self.adb.get_process_info(process_name)
389    retries_left = num_retries
390    while pid == None and retries_left != 0:
391      (pid, user) = self.adb.get_process_info(process_name)
392      time.sleep(retry_delay)
393      retries_left -= 1
394
395    return pid
396
397  def _gdbcmd(self, cmd, from_tty=False):
398    if self.verbose:
399      print '### GDB Command: ' + str(cmd)
400
401    gdb.execute(cmd, from_tty)
402
403  # Remove scratch directory if any
404  def _cleanup_temp(self):
405    if self.temp_dir:
406      shutil.rmtree(self.temp_dir)
407      self.temp_dir = None
408
409  def _cleanup_jdb(self):
410    if self.jdb_handle:
411      try:
412        self.jdb_handle.terminate()
413      except OSError, e:
414        # JDB process has likely died
415        pass
416
417      self.jdb_handle = None
418
419  def _load_local_libs(self):
420    for lib in _interesting_libs():
421      self._gdbcmd("shar " + lib)
422
423  def __del__(self):
424    self._cleanup_temp()
425    self._cleanup_jdb()
426
427  def __init__ (self, adb, name="start-android-app", cat=gdb.COMMAND_RUNNING, verbose=False):
428    super (StartAndroidApp, self).__init__(adb, name, cat, verbose)
429    self.adb = adb
430
431    self.jdb_handle = None
432    # TODO: handle possibility that port 8700 is in use (may help with
433    # Eclipse problems)
434    self.jdwp_port = 8700
435
436    # Port for gdbserver
437    self.gdbserver_port = 5039
438
439    self.temp_dir = None
440
441  def start_process(self, start_running=False):
442    #TODO: implement libbcc cache removal if needed
443
444    args = ["am", "start"]
445
446    # If we are to start running, we can take advantage of am's -W flag to wait
447    # for the process to start before returning. That way, we don't have to
448    # emulate the behaviour (poorly) through the sleep-loop below.
449    if not start_running:
450      args.append("-D")
451    else:
452      args.append("-W")
453
454    args.append(self.process_name + "/" + self.intent)
455    am_output = self.adb._shell(*args)
456    if "Error:" in am_output:
457      raise gdb.GdbError("Cannot start app. Activity Manager returned:\n"\
458        + am_output)
459
460    # Gotta wait until the process starts if we can't use -W
461    if not start_running:
462      self.pid = self._wait_for_process(self.process_name)
463
464    if not self.pid:
465      raise gdb.GdbError("Unable to detect running app remotely." \
466        + "Is " + self.process_name + " installed correctly?")
467
468    if self.verbose:
469      print "--==Android App Started: " + self.process_name \
470        + " (pid=" + self.pid + ")==--"
471
472    # Forward port for java debugger to Dalvik
473    self.adb.forward("tcp:" + str(self.jdwp_port), \
474                     "jdwp:" + str(self.pid))
475
476  def start_gdbserver(self):
477    # TODO: adjust for architecture...
478    gdbserver_local_path = os.path.join(os.getenv('ANDROID_BUILD_TOP'),
479      'prebuilt', 'android-arm', 'gdbserver', 'gdbserver')
480
481    if not self.adb.exists(self.gdbserver_path):
482      # Install gdbserver
483      try:
484        self.adb.push(gdbserver_local_path, self.gdbserver_path)
485      except gdb.GdbError, e:
486        print "Unable to push gdbserver to device. Try re-installing app."
487        raise e
488
489    self.adb._background_shell(*[self.gdbserver_path, "--attach",
490      ":" + str(self.gdbserver_port), self.pid])
491
492    self._wait_for_process(self.gdbserver_path)
493    self._update_status()
494
495    if self.verbose:
496      print "--==Remote gdbserver Started " \
497        + " (pid=" + str(self.gdbserver_pid) \
498        + " port=" + str(self.gdbserver_port) + ") ==--"
499
500    # Forward port for gdbserver
501    self.adb.forward("tcp:" + str(self.gdbserver_port), \
502                     "tcp:" + str(5039))
503
504  def attach_gdb(self, from_tty):
505    self._gdbcmd("target remote :" + str(self.gdbserver_port), False)
506    if self.verbose:
507      print "--==GDB Plugin requested attach (port=" \
508        + str(self.gdbserver_port) + ")==-"
509
510    # If GDB has no file set, things start breaking...so grab the same
511    # binary the NDK grabs from the filesystem and continue
512    self._cleanup_temp()
513    self.temp_dir = tempfile.mkdtemp()
514    self.gdb_inferior = os.path.join(self.temp_dir, 'app_process')
515    self.adb.pull("/system/bin/app_process", self.gdb_inferior)
516    self._gdbcmd('file ' + self.gdb_inferior)
517
518  def start_jdb(self, port):
519    # Kill if running
520    self._cleanup_jdb()
521
522    # Start the java debugger
523    args = ["jdb", "-connect",
524      "com.sun.jdi.SocketAttach:hostname=localhost,port=" + str(port)]
525    if self.verbose:
526      self.jdb_handle = subprocess.Popen(args, \
527        stdin=subprocess.PIPE)
528    else:
529      # Unix-only bit here..
530      self.jdb_handle = subprocess.Popen(args, \
531        stdin=subprocess.PIPE,
532        stderr=subprocess.STDOUT,
533        stdout=open('/dev/null', 'w'))
534
535  def invoke (self, arg, from_tty):
536    # TODO: self._check_app_is_installed()
537    self._check_app_is_loaded()
538
539    self.intent = currentAppInfo.get_intent()
540    self.process_name = currentAppInfo.get_name()
541    self.data_directory = currentAppInfo.get_data_directory()
542    self.gdbserver_path = currentAppInfo.get_gdbserver_path()
543
544    self._update_status()
545
546    if self.gdbserver_running:
547      self.adb.kill_by_name(self.gdbserver_path)
548      if self.verbose:
549        print "--==Killed gdbserver process (pid=" \
550          + str(self.gdbserver_pid) + ")==--"
551      self._update_status()
552
553    if self.running:
554      self.adb.kill_by_name(self.process_name)
555      if self.verbose:
556        print "--==Killed app process (pid=" + str(self.pid) + ")==--"
557      self._update_status()
558
559    self.start_process()
560
561    # Start remote gdbserver
562    self.start_gdbserver()
563
564    # Attach the gdb
565    self.attach_gdb(from_tty)
566
567    # Load symbolic libraries
568    self._load_local_libs()
569
570    # Set the debug output directory (for JIT debugging)
571    if enable_renderscript_dumps:
572      self._gdbcmd('set gDebugDumpDirectory="' + self.data_directory + '"')
573
574    # Start app
575    # unblock the gdb by connecting with jdb
576    self.start_jdb(self.jdwp_port)
577
578class RunAndroidApp(StartAndroidApp):
579  """Implements the run-android-app gdb command."""
580
581  def __init__(self, adb, name="run-android-app", cat=gdb.COMMAND_RUNNING, verbose=False):
582    super (RunAndroidApp, self).__init__(adb, name, cat, verbose)
583
584  def invoke(self, arg, from_tty):
585    StartAndroidApp.invoke(self, arg, from_tty)
586    self._gdbcmd("continue")
587
588class AttachAndroidApp(StartAndroidApp):
589  """Implements the attach-android-app gdb command."""
590
591  def __init__(self, adb, name="attach-android-app", cat=gdb.COMMAND_RUNNING, verbose=False):
592    super (AttachAndroidApp, self).__init__(adb, name, cat, verbose)
593
594  def invoke(self, arg, from_tty):
595    # TODO: self._check_app_is_installed()
596    self._check_app_is_loaded()
597
598    self.intent = currentAppInfo.get_intent()
599    self.process_name = currentAppInfo.get_name()
600    self.data_directory = currentAppInfo.get_data_directory()
601    self.gdbserver_path = currentAppInfo.get_gdbserver_path()
602
603    self._update_status()
604
605    if self.gdbserver_running:
606      self.adb.kill_by_name(self.gdbserver_path)
607      if self.verbose:
608        print "--==Killed gdbserver process (pid=" \
609          + str(self.gdbserver_pid) + ")==--"
610      self._update_status()
611
612    # Start remote gdbserver
613    self.start_gdbserver()
614
615    # Attach the gdb
616    self.attach_gdb(from_tty)
617
618    # Load symbolic libraries
619    self._load_local_libs()
620
621    # Set the debug output directory (for JIT debugging)
622    if enable_renderscript_dumps:
623      self._gdbcmd('set gDebugDumpDirectory="' + self.data_directory + '"')
624
625class LoadApp(AndroidStatus):
626  """ Implements the load-android-app gbd command.
627  """
628  def _awk_script_path(self, script_name):
629    if os.path.exists(script_name):
630      return script_name
631
632    script_root = os.path.join(os.getenv('ANDROID_BUILD_TOP'), \
633      'ndk', 'build', 'awk')
634
635    path_in_root = os.path.join(script_root, script_name)
636    if os.path.exists(path_in_root):
637      return path_in_root
638
639    raise gdb.GdbError("Unable to find awk script " \
640      +  str(script_name) + " in " + path_in_root)
641
642  def _awk(self, script, command):
643    args = ["awk", "-f", self._awk_script_path(script), str(command)]
644
645    if self.verbose:
646      print "### awk command: " + str(args)
647
648    awk_output = ""
649    try:
650      awk_output = check_output(args)
651    except subprocess.CalledProcessError, e:
652      raise gdb.GdbError("### Error in subprocess awk " + str(args))
653    except:
654      print "### Random error calling awk " + str(args)
655
656    return awk_output.rstrip()
657
658  def __init__(self, adb, name="load-android-app", cat=gdb.COMMAND_RUNNING, verbose=False):
659    super (LoadApp, self).__init__(adb, name, cat, verbose)
660    self.manifest_name = "AndroidManifest.xml"
661    self.verbose = verbose
662    self.adb = adb
663    self.temp_libdir = None
664
665  def _find_manifests(self, path):
666    manifests = []
667    for root, dirnames, filenames in os.walk(path):
668      for filename in fnmatch.filter(filenames, self.manifest_name):
669        manifests.append(os.path.join(root, filename))
670    return manifests
671
672  def _usage(self):
673    return "Usage: load-android-app [<path-to-AndroidManifest.xml>" \
674            + " | <package-name> <intent-name>]"
675
676  def invoke(self, arg, from_tty):
677
678    package_name = ''
679    launchable = ''
680    args = arg.strip('"').split()
681    if len(args) == 2:
682      package_name = args[0]
683      launchable = args[1]
684    elif len(args) == 1:
685      if os.path.isfile(args[0]) and os.path.basename(args[0]) == self.manifest_name:
686        self.manifest_path = args[0]
687      elif os.path.isdir(args[0]):
688        manifests = self._find_manifests(args[0])
689        if len(manifests) == 0:
690          raise gdb.GdbError(self.manifest_name + " not found in: " \
691            + args[0] + "\n" + self._usage())
692        elif len(manifests) > 1:
693          raise gdb.GdbError("Ambiguous argument! Found too many " \
694            + self.manifest_name + " files found:\n" + "\n".join(manifests))
695        else:
696          self.manifest_path = manifests[0]
697      else:
698        raise gdb.GdbError("Invalid path: " + args[0] + "\n" + self._usage())
699
700      package_name = self._awk("extract-package-name.awk",
701        self.manifest_path)
702      launchable = self._awk("extract-launchable.awk",
703        self.manifest_path)
704    else:
705      raise gdb.GdbError(self._usage())
706
707
708    data_directory = self.adb._shell("run-as", package_name,
709      "/system/bin/sh", "-c", "pwd").rstrip()
710
711    if not data_directory \
712      or len(data_directory) == 0 \
713      or not self.adb.exists(data_directory):
714      data_directory = os.path.join('/data', 'data', package_name)
715      print "Warning: unable to read data directory for package " \
716        + package_name + ". Meh, defaulting to " + data_directory
717
718    currentAppInfo.set_info(package_name, launchable, data_directory)
719
720    if self.verbose:
721      print "--==Android App Loaded==--"
722      print "\tname=" + currentAppInfo.get_name()
723      print "\tintent=" + currentAppInfo.get_intent()
724
725    # TODO: Check status of app on device
726
727class SetAndroidDevice (gdb.Command):
728  def __init__(self, adb, name="set-android-device", cat=gdb.COMMAND_RUNNING, verbose=False):
729    super (SetAndroidDevice, self).__init__(name, cat)
730    self.verbose = verbose
731    self.adb = adb
732
733  def _usage(self):
734    return "Usage: set-android-device <serial>"
735
736  def invoke(self, arg, from_tty):
737    if not arg or len(arg) == 0:
738      raise gdb.GdbError(self._usage)
739
740    serial = str(arg)
741    devices = adb.devices()
742    if serial in devices:
743      adb.set_current_device(serial)
744    else:
745      raise gdb.GdbError("Invalid serial. Serial numbers of connected " \
746        + "device(s): \n" + "\n".join(devices))
747
748# Global initialization
749def initOnce(adb):
750  # Try to speed up startup by skipping most android shared objects
751  gdb.execute("set auto-solib-add 0", False);
752
753  # Set shared object search path
754  gdb.execute("set solib-search-path " + local_symbols_library_directory, False)
755
756# Global instance of the object containing the info for current app
757currentAppInfo = DebugAppInfo ()
758
759# Global instance of ADB helper
760adb = ADB(verbose=be_verbose)
761
762# Perform global initialization
763initOnce(adb)
764
765# Command registration
766StartAndroidApp (adb, "start-android-app", gdb.COMMAND_RUNNING, be_verbose)
767RunAndroidApp (adb, "run-android-app", gdb.COMMAND_RUNNING, be_verbose)
768AndroidStatus (adb, "android-status", gdb.COMMAND_OBSCURE, be_verbose)
769LoadApp (adb, "load-android-app", gdb.COMMAND_RUNNING, be_verbose)
770SetAndroidDevice (adb, "set-android-device", gdb.COMMAND_RUNNING, be_verbose)
771AttachAndroidApp (adb, "attach-android-app", gdb.COMMAND_RUNNING, be_verbose)
772