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