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=[], 146 lldb=False, chroot=""): 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 if chroot: 166 run_as_cmd = ["chroot", chroot] + run_as_cmd 167 168 # Remove the old socket file. 169 device.shell_nocheck(run_as_cmd + ["rm", debug_socket]) 170 171 # Push gdbserver to the target. 172 if gdbserver_local_path is not None: 173 device.push(gdbserver_local_path, chroot + 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(chroot + debug_socket)) 188 189 gdbserver_cmd = run_as_cmd + gdbserver_cmd 190 191 if lldb: 192 gdbserver_output_path = os.path.join(tempfile.gettempdir(), 193 "lldb-client.log") 194 print("Redirecting lldb-server output to {}".format(gdbserver_output_path)) 195 else: 196 gdbserver_output_path = os.path.join(tempfile.gettempdir(), 197 "gdbclient.log") 198 print("Redirecting gdbserver output to {}".format(gdbserver_output_path)) 199 gdbserver_output = open(gdbserver_output_path, 'w') 200 return device.shell_popen(gdbserver_cmd, stdout=gdbserver_output, 201 stderr=gdbserver_output) 202 203 204def get_uid(device): 205 """Gets the uid adbd runs as.""" 206 line, _ = device.shell(["id", "-u"]) 207 return int(line.strip()) 208 209 210def forward_gdbserver_port(device, local, remote): 211 """Forwards local TCP port `port` to `remote` via `adb forward`.""" 212 if get_uid(device) != 0: 213 WARNING = '\033[93m' 214 ENDC = '\033[0m' 215 print(WARNING + 216 "Port forwarding may not work because adbd is not running as root. " + 217 " Run `adb root` to fix." + ENDC) 218 device.forward("tcp:{}".format(local), remote) 219 atexit.register(lambda: device.forward_remove("tcp:{}".format(local))) 220 221 222def find_file(device, executable_path, sysroot, run_as_cmd=None): 223 """Finds a device executable file. 224 225 This function first attempts to find the local file which will 226 contain debug symbols. If that fails, it will fall back to 227 downloading the stripped file from the device. 228 229 Args: 230 device: the AndroidDevice object to use. 231 executable_path: absolute path to the executable or symlink. 232 sysroot: absolute path to the built symbol sysroot. 233 run_as_cmd: if necessary, run-as or su command to prepend 234 235 Returns: 236 A tuple containing (<open file object>, <was found locally>). 237 238 Raises: 239 RuntimeError: could not find the executable binary. 240 ValueError: |executable_path| is not absolute. 241 """ 242 if not os.path.isabs(executable_path): 243 raise ValueError("'{}' is not an absolute path".format(executable_path)) 244 245 def generate_files(): 246 """Yields (<file name>, <found locally>) tuples.""" 247 # First look locally to avoid shelling into the device if possible. 248 # os.path.join() doesn't combine absolute paths, use + instead. 249 yield (sysroot + executable_path, True) 250 251 # Next check if the path is a symlink. 252 try: 253 target = device.shell(['readlink', '-e', '-n', executable_path])[0] 254 yield (sysroot + target, True) 255 except adb.ShellError: 256 pass 257 258 # Last, download the stripped executable from the device if necessary. 259 file_name = "gdbclient-binary-{}".format(os.getppid()) 260 remote_temp_path = "/data/local/tmp/{}".format(file_name) 261 local_path = os.path.join(tempfile.gettempdir(), file_name) 262 263 cmd = ["cat", executable_path, ">", remote_temp_path] 264 if run_as_cmd: 265 cmd = run_as_cmd + cmd 266 267 try: 268 device.shell(cmd) 269 except adb.ShellError: 270 raise RuntimeError("Failed to copy '{}' to temporary folder on " 271 "device".format(executable_path)) 272 device.pull(remote_temp_path, local_path) 273 yield (local_path, False) 274 275 for path, found_locally in generate_files(): 276 if os.path.isfile(path): 277 return (open(path, "rb"), found_locally) 278 raise RuntimeError('Could not find executable {}'.format(executable_path)) 279 280def find_executable_path(device, executable_name, run_as_cmd=None): 281 """Find a device executable from its name 282 283 This function calls which on the device to retrieve the absolute path of 284 the executable. 285 286 Args: 287 device: the AndroidDevice object to use. 288 executable_name: the name of the executable to find. 289 run_as_cmd: if necessary, run-as or su command to prepend 290 291 Returns: 292 The absolute path of the executable. 293 294 Raises: 295 RuntimeError: could not find the executable. 296 """ 297 cmd = ["which", executable_name] 298 if run_as_cmd: 299 cmd = run_as_cmd + cmd 300 301 try: 302 output, _ = device.shell(cmd) 303 return adb.split_lines(output)[0] 304 except adb.ShellError: 305 raise RuntimeError("Could not find executable '{}' on " 306 "device".format(executable_name)) 307 308def find_binary(device, pid, sysroot, run_as_cmd=None): 309 """Finds a device executable file corresponding to |pid|.""" 310 return find_file(device, "/proc/{}/exe".format(pid), sysroot, run_as_cmd) 311 312 313def get_binary_arch(binary_file): 314 """Parse a binary's ELF header for arch.""" 315 try: 316 binary_file.seek(0) 317 binary = binary_file.read(0x14) 318 except IOError: 319 raise RuntimeError("failed to read binary file") 320 ei_class = binary[0x4] # 1 = 32-bit, 2 = 64-bit 321 ei_data = binary[0x5] # Endianness 322 323 assert ei_class == 1 or ei_class == 2 324 if ei_data != 1: 325 raise RuntimeError("binary isn't little-endian?") 326 327 e_machine = binary[0x13] << 8 | binary[0x12] 328 if e_machine == 0x28: 329 assert ei_class == 1 330 return "arm" 331 elif e_machine == 0xB7: 332 assert ei_class == 2 333 return "arm64" 334 elif e_machine == 0x03: 335 assert ei_class == 1 336 return "x86" 337 elif e_machine == 0x3E: 338 assert ei_class == 2 339 return "x86_64" 340 else: 341 raise RuntimeError("unknown architecture: 0x{:x}".format(e_machine)) 342 343 344def get_binary_interp(binary_path, llvm_readobj_path): 345 args = [llvm_readobj_path, "--elf-output-style=GNU", "-l", binary_path] 346 output = subprocess.check_output(args, universal_newlines=True) 347 m = re.search(r"\[Requesting program interpreter: (.*?)\]\n", output) 348 if m is None: 349 return None 350 else: 351 return m.group(1) 352 353 354def start_gdb(gdb_path, gdb_commands, gdb_flags=None, lldb=False): 355 """Start gdb in the background and block until it finishes. 356 357 Args: 358 gdb_path: Path of the gdb binary. 359 gdb_commands: Contents of GDB script to run. 360 gdb_flags: List of flags to append to gdb command. 361 """ 362 363 # Windows disallows opening the file while it's open for writing. 364 script_fd, script_path = tempfile.mkstemp() 365 os.write(script_fd, gdb_commands.encode()) 366 os.close(script_fd) 367 if lldb: 368 script_parameter = "--source" 369 else: 370 script_parameter = "-x" 371 gdb_args = [gdb_path, script_parameter, script_path] + (gdb_flags or []) 372 373 creationflags = 0 374 if sys.platform.startswith("win"): 375 creationflags = subprocess.CREATE_NEW_CONSOLE 376 377 gdb_process = subprocess.Popen(gdb_args, creationflags=creationflags) 378 while gdb_process.returncode is None: 379 try: 380 gdb_process.communicate() 381 except KeyboardInterrupt: 382 pass 383 384 os.unlink(script_path) 385