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