You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
772 lines
25 KiB
772 lines
25 KiB
#
|
|
# 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', 'libart']
|
|
|
|
# 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-arm', '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)
|