Daniel Malea | 094881f | 2011-12-14 17:39:16 -0500 | [diff] [blame] | 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(): |
Brian Carlstrom | cb2d2e7 | 2014-06-19 13:55:16 -0700 | [diff] [blame] | 51 | return ['libc', 'libbcc', 'libRS', 'libandroid_runtime', 'libart'] |
Daniel Malea | 094881f | 2011-12-14 17:39:16 -0500 | [diff] [blame] | 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'), |
Stephen Hines | a663b77 | 2012-04-02 17:12:01 -0700 | [diff] [blame] | 479 | 'prebuilt', 'android-arm', 'gdbserver', 'gdbserver') |
Daniel Malea | 094881f | 2011-12-14 17:39:16 -0500 | [diff] [blame] | 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) |