1# 2# Copyright (C) 2015 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"""Helpers used by both gdbclient.py and ndk-gdb.py.""" 18 19import adb 20import argparse 21import atexit 22import os 23import re 24import subprocess 25import sys 26import tempfile 27 28class ArgumentParser(argparse.ArgumentParser): 29 """ArgumentParser subclass that provides adb device selection.""" 30 31 def __init__(self): 32 super(ArgumentParser, self).__init__() 33 self.add_argument( 34 "--adb", dest="adb_path", 35 help="use specific adb command") 36 37 group = self.add_argument_group(title="device selection") 38 group = group.add_mutually_exclusive_group() 39 group.add_argument( 40 "-a", action="store_const", dest="device", const="-a", 41 help="directs commands to all interfaces") 42 group.add_argument( 43 "-d", action="store_const", dest="device", const="-d", 44 help="directs commands to the only connected USB device") 45 group.add_argument( 46 "-e", action="store_const", dest="device", const="-e", 47 help="directs commands to the only connected emulator") 48 group.add_argument( 49 "-s", metavar="SERIAL", action="store", dest="serial", 50 help="directs commands to device/emulator with the given serial") 51 52 def parse_args(self, args=None, namespace=None): 53 result = super(ArgumentParser, self).parse_args(args, namespace) 54 55 adb_path = result.adb_path or "adb" 56 57 # Try to run the specified adb command 58 try: 59 subprocess.check_output([adb_path, "version"], 60 stderr=subprocess.STDOUT) 61 except (OSError, subprocess.CalledProcessError): 62 msg = "ERROR: Unable to run adb executable (tried '{}')." 63 if not result.adb_path: 64 msg += "\n Try specifying its location with --adb." 65 sys.exit(msg.format(adb_path)) 66 67 try: 68 if result.device == "-a": 69 result.device = adb.get_device(adb_path=adb_path) 70 elif result.device == "-d": 71 result.device = adb.get_usb_device(adb_path=adb_path) 72 elif result.device == "-e": 73 result.device = adb.get_emulator_device(adb_path=adb_path) 74 else: 75 result.device = adb.get_device(result.serial, adb_path=adb_path) 76 except (adb.DeviceNotFoundError, adb.NoUniqueDeviceError, RuntimeError): 77 # Don't error out if we can't find a device. 78 result.device = None 79 80 return result 81 82 83def get_processes(device): 84 """Return a dict from process name to list of running PIDs on the device.""" 85 86 # Some custom ROMs use busybox instead of toolbox for ps. Without -w, 87 # busybox truncates the output, and very long package names like 88 # com.exampleisverylongtoolongbyfar.plasma exceed the limit. 89 # 90 # API 26 use toybox instead of toolbox for ps and needs -A to list 91 # all processes. 92 # 93 # Perform the check for this on the device to avoid an adb roundtrip 94 # Some devices might not have readlink or which, so we need to handle 95 # this as well. 96 # 97 # Gracefully handle [ or readlink being missing by always using `ps` if 98 # readlink is missing. (API 18 has [, but not readlink). 99 100 ps_script = """ 101 if $(ls /system/bin/readlink >/dev/null 2>&1); then 102 if [ $(readlink /system/bin/ps) == "busybox" ]; then 103 ps -w; 104 elif [ $(readlink /system/bin/ps) == "toybox" ]; then 105 ps -A; 106 else 107 ps; 108 fi 109 else 110 ps; 111 fi 112 """ 113 ps_script = " ".join([line.strip() for line in ps_script.splitlines()]) 114 115 output, _ = device.shell([ps_script]) 116 return parse_ps_output(output) 117 118 119def parse_ps_output(output): 120 processes = dict() 121 output = adb.split_lines(output.replace("\r", "")) 122 columns = output.pop(0).split() 123 try: 124 pid_column = columns.index("PID") 125 except ValueError: 126 pid_column = 1 127 while output: 128 columns = output.pop().split() 129 process_name = columns[-1] 130 pid = int(columns[pid_column]) 131 if process_name in processes: 132 processes[process_name].append(pid) 133 else: 134 processes[process_name] = [pid] 135 136 return processes 137 138 139def get_pids(device, process_name): 140 processes = get_processes(device) 141 return processes.get(process_name, []) 142 143 144def start_gdbserver(device, gdbserver_local_path, gdbserver_remote_path, 145 target_pid, run_cmd, debug_socket, port, run_as_cmd=None, 146 lldb=False): 147 """Start gdbserver in the background and forward necessary ports. 148 149 Args: 150 device: ADB device to start gdbserver on. 151 gdbserver_local_path: Host path to push gdbserver from, can be None. 152 gdbserver_remote_path: Device path to push gdbserver to. 153 target_pid: PID of device process to attach to. 154 run_cmd: Command to run on the device. 155 debug_socket: Device path to place gdbserver unix domain socket. 156 port: Host port to forward the debug_socket to. 157 run_as_cmd: run-as or su command to prepend to commands. 158 159 Returns: 160 Popen handle to the `adb shell` process gdbserver was started with. 161 """ 162 163 assert target_pid is None or run_cmd is None 164 165 # Remove the old socket file. 166 rm_cmd = ["rm", debug_socket] 167 if run_as_cmd: 168 rm_cmd = run_as_cmd + rm_cmd 169 device.shell_nocheck(rm_cmd) 170 171 # Push gdbserver to the target. 172 if gdbserver_local_path is not None: 173 device.push(gdbserver_local_path, gdbserver_remote_path) 174 175 # Run gdbserver. 176 gdbserver_cmd = [gdbserver_remote_path] 177 if lldb: 178 gdbserver_cmd.extend(["gdbserver", "unix://" + debug_socket]) 179 else: 180 gdbserver_cmd.extend(["--once", "+{}".format(debug_socket)]) 181 182 if target_pid is not None: 183 gdbserver_cmd += ["--attach", str(target_pid)] 184 else: 185 gdbserver_cmd += run_cmd 186 187 forward_gdbserver_port(device, local=port, remote="localfilesystem:{}".format(debug_socket)) 188 189 if run_as_cmd: 190 gdbserver_cmd = run_as_cmd + gdbserver_cmd 191 192 gdbserver_output_path = os.path.join(tempfile.gettempdir(), 193 "gdbclient.log") 194 print("Redirecting gdbserver output to {}".format(gdbserver_output_path)) 195 gdbserver_output = file(gdbserver_output_path, 'w') 196 return device.shell_popen(gdbserver_cmd, stdout=gdbserver_output, 197 stderr=gdbserver_output) 198 199 200def forward_gdbserver_port(device, local, remote): 201 """Forwards local TCP port `port` to `remote` via `adb forward`.""" 202 device.forward("tcp:{}".format(local), remote) 203 atexit.register(lambda: device.forward_remove("tcp:{}".format(local))) 204 205 206def find_file(device, executable_path, sysroot, run_as_cmd=None): 207 """Finds a device executable file. 208 209 This function first attempts to find the local file which will 210 contain debug symbols. If that fails, it will fall back to 211 downloading the stripped file from the device. 212 213 Args: 214 device: the AndroidDevice object to use. 215 executable_path: absolute path to the executable or symlink. 216 sysroot: absolute path to the built symbol sysroot. 217 run_as_cmd: if necessary, run-as or su command to prepend 218 219 Returns: 220 A tuple containing (<open file object>, <was found locally>). 221 222 Raises: 223 RuntimeError: could not find the executable binary. 224 ValueError: |executable_path| is not absolute. 225 """ 226 if not os.path.isabs(executable_path): 227 raise ValueError("'{}' is not an absolute path".format(executable_path)) 228 229 def generate_files(): 230 """Yields (<file name>, <found locally>) tuples.""" 231 # First look locally to avoid shelling into the device if possible. 232 # os.path.join() doesn't combine absolute paths, use + instead. 233 yield (sysroot + executable_path, True) 234 235 # Next check if the path is a symlink. 236 try: 237 target = device.shell(['readlink', '-e', '-n', executable_path])[0] 238 yield (sysroot + target, True) 239 except adb.ShellError: 240 pass 241 242 # Last, download the stripped executable from the device if necessary. 243 file_name = "gdbclient-binary-{}".format(os.getppid()) 244 remote_temp_path = "/data/local/tmp/{}".format(file_name) 245 local_path = os.path.join(tempfile.gettempdir(), file_name) 246 247 cmd = ["cat", executable_path, ">", remote_temp_path] 248 if run_as_cmd: 249 cmd = run_as_cmd + cmd 250 251 try: 252 device.shell(cmd) 253 except adb.ShellError: 254 raise RuntimeError("Failed to copy '{}' to temporary folder on " 255 "device".format(executable_path)) 256 device.pull(remote_temp_path, local_path) 257 yield (local_path, False) 258 259 for path, found_locally in generate_files(): 260 if os.path.isfile(path): 261 return (open(path, "r"), found_locally) 262 raise RuntimeError('Could not find executable {}'.format(executable_path)) 263 264def find_executable_path(device, executable_name, run_as_cmd=None): 265 """Find a device executable from its name 266 267 This function calls which on the device to retrieve the absolute path of 268 the executable. 269 270 Args: 271 device: the AndroidDevice object to use. 272 executable_name: the name of the executable to find. 273 run_as_cmd: if necessary, run-as or su command to prepend 274 275 Returns: 276 The absolute path of the executable. 277 278 Raises: 279 RuntimeError: could not find the executable. 280 """ 281 cmd = ["which", executable_name] 282 if run_as_cmd: 283 cmd = run_as_cmd + cmd 284 285 try: 286 output, _ = device.shell(cmd) 287 return adb.split_lines(output)[0] 288 except adb.ShellError: 289 raise RuntimeError("Could not find executable '{}' on " 290 "device".format(executable_name)) 291 292def find_binary(device, pid, sysroot, run_as_cmd=None): 293 """Finds a device executable file corresponding to |pid|.""" 294 return find_file(device, "/proc/{}/exe".format(pid), sysroot, run_as_cmd) 295 296 297def get_binary_arch(binary_file): 298 """Parse a binary's ELF header for arch.""" 299 try: 300 binary_file.seek(0) 301 binary = binary_file.read(0x14) 302 except IOError: 303 raise RuntimeError("failed to read binary file") 304 ei_class = ord(binary[0x4]) # 1 = 32-bit, 2 = 64-bit 305 ei_data = ord(binary[0x5]) # Endianness 306 307 assert ei_class == 1 or ei_class == 2 308 if ei_data != 1: 309 raise RuntimeError("binary isn't little-endian?") 310 311 e_machine = ord(binary[0x13]) << 8 | ord(binary[0x12]) 312 if e_machine == 0x28: 313 assert ei_class == 1 314 return "arm" 315 elif e_machine == 0xB7: 316 assert ei_class == 2 317 return "arm64" 318 elif e_machine == 0x03: 319 assert ei_class == 1 320 return "x86" 321 elif e_machine == 0x3E: 322 assert ei_class == 2 323 return "x86_64" 324 elif e_machine == 0x08: 325 if ei_class == 1: 326 return "mips" 327 else: 328 return "mips64" 329 else: 330 raise RuntimeError("unknown architecture: 0x{:x}".format(e_machine)) 331 332 333def get_binary_interp(binary_path, llvm_readobj_path): 334 args = [llvm_readobj_path, "--elf-output-style=GNU", "-l", binary_path] 335 output = subprocess.check_output(args, universal_newlines=True) 336 m = re.search(r"\[Requesting program interpreter: (.*?)\]\n", output) 337 if m is None: 338 return None 339 else: 340 return m.group(1) 341 342 343def start_gdb(gdb_path, gdb_commands, gdb_flags=None, lldb=False): 344 """Start gdb in the background and block until it finishes. 345 346 Args: 347 gdb_path: Path of the gdb binary. 348 gdb_commands: Contents of GDB script to run. 349 gdb_flags: List of flags to append to gdb command. 350 """ 351 352 # Windows disallows opening the file while it's open for writing. 353 script_fd, script_path = tempfile.mkstemp() 354 os.write(script_fd, gdb_commands) 355 os.close(script_fd) 356 if lldb: 357 script_parameter = "--source" 358 else: 359 script_parameter = "-x" 360 gdb_args = [gdb_path, script_parameter, script_path] + (gdb_flags or []) 361 362 creationflags = 0 363 if sys.platform.startswith("win"): 364 creationflags = subprocess.CREATE_NEW_CONSOLE 365 env = dict(os.environ) 366 if lldb: 367 bin_dir = os.path.dirname(gdb_path) 368 if sys.platform.startswith("win"): 369 python_path = os.path.join(bin_dir, "../lib/site-packages") 370 else: 371 python_path = os.path.join(bin_dir, "../lib/python2.7/site-packages") 372 env['PYTHONPATH'] = os.path.normpath(python_path) 373 374 gdb_process = subprocess.Popen(gdb_args, creationflags=creationflags, env=env) 375 while gdb_process.returncode is None: 376 try: 377 gdb_process.communicate() 378 except KeyboardInterrupt: 379 pass 380 381 os.unlink(script_path)