| # |
| # Copyright (C) 2012 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| # |
| # GDB plugin to allow debugging of apps on remote Android systems using gdbserver. |
| # |
| # To use this plugin, source this file from a Python-enabled GDB client, then use: |
| # load-android-app <app-source-dir> to tell GDB about the app you are debugging |
| # run-android-app to start the app in a running state |
| # start-android-app to start the app in a paused state |
| # attach-android-ap to attach to an existing (running) instance of app |
| # set-android-device to select a target (only if multiple devices are attached) |
| |
| import fnmatch |
| import gdb |
| import os |
| import shutil |
| import subprocess |
| import tempfile |
| import time |
| |
| be_verbose = False |
| enable_renderscript_dumps = True |
| local_symbols_library_directory = os.path.join(os.getenv('ANDROID_PRODUCT_OUT', 'out'), |
| 'symbols', 'system', 'lib') |
| local_library_directory = os.path.join(os.getenv('ANDROID_PRODUCT_OUT', 'out'), |
| 'system', 'lib') |
| |
| # ADB - Basic ADB wrapper, far from complete |
| # DebugAppInfo - App configuration struct, as far as GDB cares |
| # StartAndroidApp - Implementation of GDB start (for android apps) |
| # RunAndroidApp - Implementation of GDB run (for android apps) |
| # AttachAndroidApp - GDB command to attach to an existing android app process |
| # AndroidStatus - app status query command (not needed, mostly harmless) |
| # LoadAndroidApp - Sets the package and intent names for an app |
| |
| def _interesting_libs(): |
| return ['libc', 'libbcc', 'libRS', 'libandroid_runtime', 'libdvm'] |
| |
| # In python 2.6, subprocess.check_output does not exist, so it is implemented here |
| def check_output(*popenargs, **kwargs): |
| p = subprocess.Popen(stdout=subprocess.PIPE, stderr=subprocess.STDOUT, *popenargs, **kwargs) |
| out, err = p.communicate() |
| retcode = p.poll() |
| if retcode != 0: |
| c = kwargs.get("args") |
| if c is None: |
| c = popenargs[0] |
| e = subprocess.CalledProcessError(retcode, c) |
| e.output = str(out) + str(err) |
| raise e |
| return out |
| |
| class DebugAppInfo: |
| """Stores information from an app manifest""" |
| |
| def __init__(self): |
| self.name = None |
| self.intent = None |
| |
| def get_name(self): |
| return self.name |
| |
| def get_intent(self): |
| return self.intent |
| |
| def get_data_directory(self): |
| return self.data_directory |
| |
| def get_gdbserver_path(self): |
| return os.path.join(self.data_directory, "lib", "gdbserver") |
| |
| def set_info(self, name, intent, data_directory): |
| self.name = name |
| self.intent = intent |
| self.data_directory = data_directory |
| |
| def unset_info(): |
| self.name = None |
| self.intent = None |
| self.data_directory = None |
| |
| class ADB: |
| """ |
| Python class implementing a basic ADB wrapper for useful commands. |
| Uses subprocess to invoke adb. |
| """ |
| |
| def __init__(self, device=None, verbose=False): |
| self.verbose = verbose |
| self.current_device = device |
| self.temp_libdir = None |
| self.background_processes = [] |
| self.android_build_top = os.getenv('ANDROID_BUILD_TOP', None) |
| if not self.android_build_top: |
| raise gdb.GdbError("Unable to read ANDROID_BUILD_TOP. " \ |
| + "Is your environment setup correct?") |
| |
| self.adb_path = os.path.join(self.android_build_top, |
| 'out', 'host', 'linux-x86', 'bin', 'adb') |
| |
| if not self.current_device: |
| devices = self.devices() |
| if len(devices) == 1: |
| self.set_current_device(devices[0]) |
| return |
| else: |
| msg = "" |
| if len(devices) == 0: |
| msg = "No devices detected. Please connect a device and " |
| else: |
| msg = "Too many devices (" + ", ".join(devices) + ") detected. " \ |
| + "Please " |
| |
| print "Warning: " + msg + " use the set-android-device command." |
| |
| |
| def _prepare_adb_args(self, args): |
| largs = list(args) |
| |
| # Prepare serial number option from current_device |
| if self.current_device and len(self.current_device) > 0: |
| largs.insert(0, self.current_device) |
| largs.insert(0, "-s") |
| |
| largs.insert(0, self.adb_path) |
| return largs |
| |
| |
| def _background_adb(self, *args): |
| largs = self._prepare_adb_args(args) |
| p = None |
| try: |
| if self.verbose: |
| print "### " + str(largs) |
| p = subprocess.Popen(largs) |
| self.background_processes.append(p) |
| except CalledProcessError, e: |
| raise gdb.GdbError("Error starting background adb " + str(largs)) |
| except: |
| raise gdb.GdbError("Unknown error starting background adb " + str(largs)) |
| |
| return p |
| |
| def _call_adb(self, *args): |
| output = "" |
| largs = self._prepare_adb_args(args) |
| try: |
| if self.verbose: |
| print "### " + str(largs) |
| output = check_output(largs) |
| except subprocess.CalledProcessError, e: |
| raise gdb.GdbError("Error starting adb " + str(largs)) |
| except Exception as e: |
| raise gdb.GdbError("Unknown error starting adb " + str(largs)) |
| |
| return output |
| |
| def _shell(self, *args): |
| args = ["shell"] + list(args) |
| return self._call_adb(*args) |
| |
| def _background_shell(self, *args): |
| args = ["shell"] + list(args) |
| return self._background_adb(*args) |
| |
| def _cleanup_background_processes(self): |
| for handle in self.background_processes: |
| try: |
| handle.terminate() |
| except OSError, e: |
| # Background process died already |
| pass |
| |
| def _cleanup_temp(self): |
| if self.temp_libdir: |
| shutil.rmtree(self.temp_libdir) |
| self.temp_libdir = None |
| |
| def __del__(self): |
| self._cleanup_temp() |
| self._cleanup_background_processes() |
| |
| def _get_local_libs(self): |
| ret = [] |
| for lib in _interesting_libs(): |
| lib_path = os.path.join(local_library_directory, lib + ".so") |
| if not os.path.exists(lib_path) and self.verbose: |
| print "Warning: unable to find expected library " \ |
| + lib_path + "." |
| ret.append(lib_path) |
| |
| return ret |
| |
| def _check_remote_libs_match_local_libs(self): |
| ret = [] |
| all_remote_libs = self._shell("ls", "/system/lib/*.so").split() |
| local_libs = self._get_local_libs() |
| |
| self.temp_libdir = tempfile.mkdtemp() |
| |
| for lib in _interesting_libs(): |
| lib += ".so" |
| for remote_lib in all_remote_libs: |
| if lib in remote_lib: |
| # Pull lib from device and compute hash |
| tmp_path = os.path.join(self.temp_libdir, lib) |
| self.pull(remote_lib, tmp_path) |
| remote_hash = self._md5sum(tmp_path) |
| |
| # Find local lib and compute hash |
| built_library = filter(lambda l: lib in l, local_libs)[0] |
| built_hash = self._md5sum(built_library) |
| |
| # Alert user if library mismatch is detected |
| if built_hash != remote_hash: |
| self._cleanup_temp() |
| raise gdb.GdbError("Library mismatch between:\n" \ |
| + "\t(" + remote_hash + ") " + tmp_path + " (from target) and\n " \ |
| + "\t(" + built_hash + ") " + built_library + " (on host)\n" \ |
| + "The target is running a different build than the host." \ |
| + " This situation is not debuggable.") |
| |
| self._cleanup_temp() |
| |
| def _md5sum(self, file): |
| try: |
| return check_output(["md5sum", file]).strip().split()[0] |
| except subprocess.CalledProcessError, e: |
| raise gdb.GdbError("Error invoking md5sum commandline utility") |
| |
| # Returns the list of serial numbers of connected devices |
| def devices(self): |
| ret = [] |
| raw_output = self._call_adb("devices").split() |
| if len(raw_output) < 5: |
| return None |
| else: |
| for serial_num_index in range(4, len(raw_output), 2): |
| ret.append(raw_output[serial_num_index]) |
| return ret |
| |
| def set_current_device(self, serial): |
| if self.current_device == str(serial): |
| print "Current device already is: " + str(serial) |
| return |
| |
| # TODO: this function should probably check the serial is valid. |
| self.current_device = str(serial) |
| |
| api_version = self.getprop("ro.build.version.sdk") |
| if api_version < 15: |
| print "Warning: untested API version. Upgrade to 15 or higher" |
| |
| # Verify the local libraries loaded by GDB are identical to those |
| # sitting on the device actually executing. Alert the user if |
| # this is happening |
| self._check_remote_libs_match_local_libs() |
| |
| # adb getprop [property] |
| # if property is not None, returns the given property, otherwise |
| # returns all properties. |
| def getprop(self, property=None): |
| if property == None: |
| # get all the props |
| return self._call_adb(*["shell", "getprop"]).split('\n') |
| else: |
| return str(self._call_adb(*["shell", "getprop", |
| str(property)]).split('\n')[0]) |
| |
| # adb push |
| def push(self, source, destination): |
| self._call_adb(*["push", source, destination]) |
| |
| # adb forward <source> <destination> |
| def forward(self, source, destination): |
| self._call_adb(*["forward", source, destination]) |
| |
| # Returns true if filename exists on Android fs, false otherwise |
| def exists(self, filename): |
| raw_listing = self._shell(*["ls", filename]) |
| return "No such file or directory" not in raw_listing |
| |
| # adb pull <remote_path> <local_path> |
| def pull(self, remote_path, local_path): |
| self._call_adb(*["pull", remote_path, local_path]) |
| |
| #wrapper for adb shell ps. leave process_name=None for list of all processes |
| #Otherwise, returns triple with process name, pid and owner, |
| def get_process_info(self, process_name=None): |
| ret = [] |
| raw_output = self._shell("ps") |
| for raw_line in raw_output.splitlines()[1:]: |
| line = raw_line.split() |
| name = line[-1] |
| |
| if process_name == None or name == process_name: |
| user = line[0] |
| pid = line[1] |
| |
| if process_name != None: |
| return (pid, user) |
| else: |
| ret.append((pid, user)) |
| |
| # No match in target process |
| if process_name != None: |
| return (None, None) |
| |
| return ret |
| |
| def kill_by_pid(self, pid): |
| self._shell(*["kill", "-9", pid]) |
| |
| def kill_by_name(self, process_name): |
| (pid, user) = self.get_process_info(process_name) |
| while pid != None: |
| self.kill_by_pid(pid) |
| (pid, user) = self.get_process_info(process_name) |
| |
| class AndroidStatus(gdb.Command): |
| """Implements the android-status gdb command.""" |
| |
| def __init__(self, adb, name="android-status", cat=gdb.COMMAND_OBSCURE, verbose=False): |
| super (AndroidStatus, self).__init__(name, cat) |
| self.verbose = verbose |
| self.adb = adb |
| |
| def _update_status(self, process_name, gdbserver_process_name): |
| self._check_app_is_loaded() |
| |
| # Update app status |
| (self.pid, self.owner_user) = \ |
| self.adb.get_process_info(process_name) |
| self.running = self.pid != None |
| |
| # Update gdbserver status |
| (self.gdbserver_pid, self.gdbserver_user) = \ |
| self.adb.get_process_info(gdbserver_process_name) |
| self.gdbserver_running = self.gdbserver_pid != None |
| |
| # Print results |
| if self.verbose: |
| print "--==Android GDB Plugin Status Update==--" |
| print "\tinferior name: " + process_name |
| print "\trunning: " + str(self.running) |
| print "\tpid: " + str(self.pid) |
| print "\tgdbserver running: " + str(self.gdbserver_running) |
| print "\tgdbserver pid: " + str(self.gdbserver_pid) |
| print "\tgdbserver user: " + str(self.gdbserver_user) |
| |
| def _check_app_is_loaded(self): |
| if not currentAppInfo.get_name(): |
| raise gdb.GdbError("Error: no app loaded. Try load-android-app.") |
| |
| def invoke(self, arg, from_tty): |
| self._check_app_is_loaded() |
| self._update_status(currentAppInfo.get_name(), |
| currentAppInfo.get_gdbserver_path()) |
| # TODO: maybe print something if verbose is off |
| |
| class StartAndroidApp (AndroidStatus): |
| """Implements the 'start-android-app' gdb command.""" |
| |
| def _update_status(self): |
| AndroidStatus._update_status(self, self.process_name, \ |
| self.gdbserver_path) |
| |
| # Calls adb shell ps every retry_delay seconds and returns |
| # the pid when process_name show up in output, or return 0 |
| # after num_retries attempts. num_retries=0 means retry |
| # indefinitely. |
| def _wait_for_process(self, process_name, retry_delay=1, num_retries=10): |
| """ This function is a hack and should not be required""" |
| (pid, user) = self.adb.get_process_info(process_name) |
| retries_left = num_retries |
| while pid == None and retries_left != 0: |
| (pid, user) = self.adb.get_process_info(process_name) |
| time.sleep(retry_delay) |
| retries_left -= 1 |
| |
| return pid |
| |
| def _gdbcmd(self, cmd, from_tty=False): |
| if self.verbose: |
| print '### GDB Command: ' + str(cmd) |
| |
| gdb.execute(cmd, from_tty) |
| |
| # Remove scratch directory if any |
| def _cleanup_temp(self): |
| if self.temp_dir: |
| shutil.rmtree(self.temp_dir) |
| self.temp_dir = None |
| |
| def _cleanup_jdb(self): |
| if self.jdb_handle: |
| try: |
| self.jdb_handle.terminate() |
| except OSError, e: |
| # JDB process has likely died |
| pass |
| |
| self.jdb_handle = None |
| |
| def _load_local_libs(self): |
| for lib in _interesting_libs(): |
| self._gdbcmd("shar " + lib) |
| |
| def __del__(self): |
| self._cleanup_temp() |
| self._cleanup_jdb() |
| |
| def __init__ (self, adb, name="start-android-app", cat=gdb.COMMAND_RUNNING, verbose=False): |
| super (StartAndroidApp, self).__init__(adb, name, cat, verbose) |
| self.adb = adb |
| |
| self.jdb_handle = None |
| # TODO: handle possibility that port 8700 is in use (may help with |
| # Eclipse problems) |
| self.jdwp_port = 8700 |
| |
| # Port for gdbserver |
| self.gdbserver_port = 5039 |
| |
| self.temp_dir = None |
| |
| def start_process(self, start_running=False): |
| #TODO: implement libbcc cache removal if needed |
| |
| args = ["am", "start"] |
| |
| # If we are to start running, we can take advantage of am's -W flag to wait |
| # for the process to start before returning. That way, we don't have to |
| # emulate the behaviour (poorly) through the sleep-loop below. |
| if not start_running: |
| args.append("-D") |
| else: |
| args.append("-W") |
| |
| args.append(self.process_name + "/" + self.intent) |
| am_output = self.adb._shell(*args) |
| if "Error:" in am_output: |
| raise gdb.GdbError("Cannot start app. Activity Manager returned:\n"\ |
| + am_output) |
| |
| # Gotta wait until the process starts if we can't use -W |
| if not start_running: |
| self.pid = self._wait_for_process(self.process_name) |
| |
| if not self.pid: |
| raise gdb.GdbError("Unable to detect running app remotely." \ |
| + "Is " + self.process_name + " installed correctly?") |
| |
| if self.verbose: |
| print "--==Android App Started: " + self.process_name \ |
| + " (pid=" + self.pid + ")==--" |
| |
| # Forward port for java debugger to Dalvik |
| self.adb.forward("tcp:" + str(self.jdwp_port), \ |
| "jdwp:" + str(self.pid)) |
| |
| def start_gdbserver(self): |
| # TODO: adjust for architecture... |
| gdbserver_local_path = os.path.join(os.getenv('ANDROID_BUILD_TOP'), |
| 'prebuilt', 'android-x86', 'gdbserver', 'gdbserver') |
| |
| if not self.adb.exists(self.gdbserver_path): |
| # Install gdbserver |
| try: |
| self.adb.push(gdbserver_local_path, self.gdbserver_path) |
| except gdb.GdbError, e: |
| print "Unable to push gdbserver to device. Try re-installing app." |
| raise e |
| |
| self.adb._background_shell(*[self.gdbserver_path, "--attach", |
| ":" + str(self.gdbserver_port), self.pid]) |
| |
| self._wait_for_process(self.gdbserver_path) |
| self._update_status() |
| |
| if self.verbose: |
| print "--==Remote gdbserver Started " \ |
| + " (pid=" + str(self.gdbserver_pid) \ |
| + " port=" + str(self.gdbserver_port) + ") ==--" |
| |
| # Forward port for gdbserver |
| self.adb.forward("tcp:" + str(self.gdbserver_port), \ |
| "tcp:" + str(5039)) |
| |
| def attach_gdb(self, from_tty): |
| self._gdbcmd("target remote :" + str(self.gdbserver_port), False) |
| if self.verbose: |
| print "--==GDB Plugin requested attach (port=" \ |
| + str(self.gdbserver_port) + ")==-" |
| |
| # If GDB has no file set, things start breaking...so grab the same |
| # binary the NDK grabs from the filesystem and continue |
| self._cleanup_temp() |
| self.temp_dir = tempfile.mkdtemp() |
| self.gdb_inferior = os.path.join(self.temp_dir, 'app_process') |
| self.adb.pull("/system/bin/app_process", self.gdb_inferior) |
| self._gdbcmd('file ' + self.gdb_inferior) |
| |
| def start_jdb(self, port): |
| # Kill if running |
| self._cleanup_jdb() |
| |
| # Start the java debugger |
| args = ["jdb", "-connect", |
| "com.sun.jdi.SocketAttach:hostname=localhost,port=" + str(port)] |
| if self.verbose: |
| self.jdb_handle = subprocess.Popen(args, \ |
| stdin=subprocess.PIPE) |
| else: |
| # Unix-only bit here.. |
| self.jdb_handle = subprocess.Popen(args, \ |
| stdin=subprocess.PIPE, |
| stderr=subprocess.STDOUT, |
| stdout=open('/dev/null', 'w')) |
| |
| def invoke (self, arg, from_tty): |
| # TODO: self._check_app_is_installed() |
| self._check_app_is_loaded() |
| |
| self.intent = currentAppInfo.get_intent() |
| self.process_name = currentAppInfo.get_name() |
| self.data_directory = currentAppInfo.get_data_directory() |
| self.gdbserver_path = currentAppInfo.get_gdbserver_path() |
| |
| self._update_status() |
| |
| if self.gdbserver_running: |
| self.adb.kill_by_name(self.gdbserver_path) |
| if self.verbose: |
| print "--==Killed gdbserver process (pid=" \ |
| + str(self.gdbserver_pid) + ")==--" |
| self._update_status() |
| |
| if self.running: |
| self.adb.kill_by_name(self.process_name) |
| if self.verbose: |
| print "--==Killed app process (pid=" + str(self.pid) + ")==--" |
| self._update_status() |
| |
| self.start_process() |
| |
| # Start remote gdbserver |
| self.start_gdbserver() |
| |
| # Attach the gdb |
| self.attach_gdb(from_tty) |
| |
| # Load symbolic libraries |
| self._load_local_libs() |
| |
| # Set the debug output directory (for JIT debugging) |
| if enable_renderscript_dumps: |
| self._gdbcmd('set gDebugDumpDirectory="' + self.data_directory + '"') |
| |
| # Start app |
| # unblock the gdb by connecting with jdb |
| self.start_jdb(self.jdwp_port) |
| |
| class RunAndroidApp(StartAndroidApp): |
| """Implements the run-android-app gdb command.""" |
| |
| def __init__(self, adb, name="run-android-app", cat=gdb.COMMAND_RUNNING, verbose=False): |
| super (RunAndroidApp, self).__init__(adb, name, cat, verbose) |
| |
| def invoke(self, arg, from_tty): |
| StartAndroidApp.invoke(self, arg, from_tty) |
| self._gdbcmd("continue") |
| |
| class AttachAndroidApp(StartAndroidApp): |
| """Implements the attach-android-app gdb command.""" |
| |
| def __init__(self, adb, name="attach-android-app", cat=gdb.COMMAND_RUNNING, verbose=False): |
| super (AttachAndroidApp, self).__init__(adb, name, cat, verbose) |
| |
| def invoke(self, arg, from_tty): |
| # TODO: self._check_app_is_installed() |
| self._check_app_is_loaded() |
| |
| self.intent = currentAppInfo.get_intent() |
| self.process_name = currentAppInfo.get_name() |
| self.data_directory = currentAppInfo.get_data_directory() |
| self.gdbserver_path = currentAppInfo.get_gdbserver_path() |
| |
| self._update_status() |
| |
| if self.gdbserver_running: |
| self.adb.kill_by_name(self.gdbserver_path) |
| if self.verbose: |
| print "--==Killed gdbserver process (pid=" \ |
| + str(self.gdbserver_pid) + ")==--" |
| self._update_status() |
| |
| # Start remote gdbserver |
| self.start_gdbserver() |
| |
| # Attach the gdb |
| self.attach_gdb(from_tty) |
| |
| # Load symbolic libraries |
| self._load_local_libs() |
| |
| # Set the debug output directory (for JIT debugging) |
| if enable_renderscript_dumps: |
| self._gdbcmd('set gDebugDumpDirectory="' + self.data_directory + '"') |
| |
| class LoadApp(AndroidStatus): |
| """ Implements the load-android-app gbd command. |
| """ |
| def _awk_script_path(self, script_name): |
| if os.path.exists(script_name): |
| return script_name |
| |
| script_root = os.path.join(os.getenv('ANDROID_BUILD_TOP'), \ |
| 'ndk', 'build', 'awk') |
| |
| path_in_root = os.path.join(script_root, script_name) |
| if os.path.exists(path_in_root): |
| return path_in_root |
| |
| raise gdb.GdbError("Unable to find awk script " \ |
| + str(script_name) + " in " + path_in_root) |
| |
| def _awk(self, script, command): |
| args = ["awk", "-f", self._awk_script_path(script), str(command)] |
| |
| if self.verbose: |
| print "### awk command: " + str(args) |
| |
| awk_output = "" |
| try: |
| awk_output = check_output(args) |
| except subprocess.CalledProcessError, e: |
| raise gdb.GdbError("### Error in subprocess awk " + str(args)) |
| except: |
| print "### Random error calling awk " + str(args) |
| |
| return awk_output.rstrip() |
| |
| def __init__(self, adb, name="load-android-app", cat=gdb.COMMAND_RUNNING, verbose=False): |
| super (LoadApp, self).__init__(adb, name, cat, verbose) |
| self.manifest_name = "AndroidManifest.xml" |
| self.verbose = verbose |
| self.adb = adb |
| self.temp_libdir = None |
| |
| def _find_manifests(self, path): |
| manifests = [] |
| for root, dirnames, filenames in os.walk(path): |
| for filename in fnmatch.filter(filenames, self.manifest_name): |
| manifests.append(os.path.join(root, filename)) |
| return manifests |
| |
| def _usage(self): |
| return "Usage: load-android-app [<path-to-AndroidManifest.xml>" \ |
| + " | <package-name> <intent-name>]" |
| |
| def invoke(self, arg, from_tty): |
| |
| package_name = '' |
| launchable = '' |
| args = arg.strip('"').split() |
| if len(args) == 2: |
| package_name = args[0] |
| launchable = args[1] |
| elif len(args) == 1: |
| if os.path.isfile(args[0]) and os.path.basename(args[0]) == self.manifest_name: |
| self.manifest_path = args[0] |
| elif os.path.isdir(args[0]): |
| manifests = self._find_manifests(args[0]) |
| if len(manifests) == 0: |
| raise gdb.GdbError(self.manifest_name + " not found in: " \ |
| + args[0] + "\n" + self._usage()) |
| elif len(manifests) > 1: |
| raise gdb.GdbError("Ambiguous argument! Found too many " \ |
| + self.manifest_name + " files found:\n" + "\n".join(manifests)) |
| else: |
| self.manifest_path = manifests[0] |
| else: |
| raise gdb.GdbError("Invalid path: " + args[0] + "\n" + self._usage()) |
| |
| package_name = self._awk("extract-package-name.awk", |
| self.manifest_path) |
| launchable = self._awk("extract-launchable.awk", |
| self.manifest_path) |
| else: |
| raise gdb.GdbError(self._usage()) |
| |
| |
| data_directory = self.adb._shell("run-as", package_name, |
| "/system/bin/sh", "-c", "pwd").rstrip() |
| |
| if not data_directory \ |
| or len(data_directory) == 0 \ |
| or not self.adb.exists(data_directory): |
| data_directory = os.path.join('/data', 'data', package_name) |
| print "Warning: unable to read data directory for package " \ |
| + package_name + ". Meh, defaulting to " + data_directory |
| |
| currentAppInfo.set_info(package_name, launchable, data_directory) |
| |
| if self.verbose: |
| print "--==Android App Loaded==--" |
| print "\tname=" + currentAppInfo.get_name() |
| print "\tintent=" + currentAppInfo.get_intent() |
| |
| # TODO: Check status of app on device |
| |
| class SetAndroidDevice (gdb.Command): |
| def __init__(self, adb, name="set-android-device", cat=gdb.COMMAND_RUNNING, verbose=False): |
| super (SetAndroidDevice, self).__init__(name, cat) |
| self.verbose = verbose |
| self.adb = adb |
| |
| def _usage(self): |
| return "Usage: set-android-device <serial>" |
| |
| def invoke(self, arg, from_tty): |
| if not arg or len(arg) == 0: |
| raise gdb.GdbError(self._usage) |
| |
| serial = str(arg) |
| devices = adb.devices() |
| if serial in devices: |
| adb.set_current_device(serial) |
| else: |
| raise gdb.GdbError("Invalid serial. Serial numbers of connected " \ |
| + "device(s): \n" + "\n".join(devices)) |
| |
| # Global initialization |
| def initOnce(adb): |
| # Try to speed up startup by skipping most android shared objects |
| gdb.execute("set auto-solib-add 0", False); |
| |
| # Set shared object search path |
| gdb.execute("set solib-search-path " + local_symbols_library_directory, False) |
| |
| # Global instance of the object containing the info for current app |
| currentAppInfo = DebugAppInfo () |
| |
| # Global instance of ADB helper |
| adb = ADB(verbose=be_verbose) |
| |
| # Perform global initialization |
| initOnce(adb) |
| |
| # Command registration |
| StartAndroidApp (adb, "start-android-app", gdb.COMMAND_RUNNING, be_verbose) |
| RunAndroidApp (adb, "run-android-app", gdb.COMMAND_RUNNING, be_verbose) |
| AndroidStatus (adb, "android-status", gdb.COMMAND_OBSCURE, be_verbose) |
| LoadApp (adb, "load-android-app", gdb.COMMAND_RUNNING, be_verbose) |
| SetAndroidDevice (adb, "set-android-device", gdb.COMMAND_RUNNING, be_verbose) |
| AttachAndroidApp (adb, "attach-android-app", gdb.COMMAND_RUNNING, be_verbose) |