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