Home | History | Annotate | Download | only in gdb_plugin
      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 
     27 import fnmatch
     28 import gdb
     29 import os
     30 import shutil
     31 import subprocess
     32 import tempfile
     33 import time
     34 
     35 be_verbose = False
     36 enable_renderscript_dumps = True
     37 local_symbols_library_directory = os.path.join(os.getenv('ANDROID_PRODUCT_OUT', 'out'),
     38       'symbols', 'system', 'lib')
     39 local_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 
     50 def _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
     54 def 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 
     67 class 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 
     96 class 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 
    334 class 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 
    375 class 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 
    578 class 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 
    588 class 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 
    625 class 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 
    727 class 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
    749 def 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
    757 currentAppInfo = DebugAppInfo ()
    758 
    759 # Global instance of ADB helper
    760 adb = ADB(verbose=be_verbose)
    761 
    762 # Perform global initialization
    763 initOnce(adb)
    764 
    765 # Command registration
    766 StartAndroidApp (adb, "start-android-app", gdb.COMMAND_RUNNING, be_verbose)
    767 RunAndroidApp (adb, "run-android-app", gdb.COMMAND_RUNNING, be_verbose)
    768 AndroidStatus (adb, "android-status", gdb.COMMAND_OBSCURE, be_verbose)
    769 LoadApp (adb, "load-android-app", gdb.COMMAND_RUNNING, be_verbose)
    770 SetAndroidDevice (adb, "set-android-device", gdb.COMMAND_RUNNING, be_verbose)
    771 AttachAndroidApp (adb, "attach-android-app", gdb.COMMAND_RUNNING, be_verbose)
    772