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