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_run_as_cmd(user, cmd): 83 """Generate a run-as or su command depending on user.""" 84 85 if user is None: 86 return cmd 87 elif user == "root": 88 return ["su", "0"] + cmd 89 else: 90 return ["run-as", user] + cmd 91 92 93def get_processes(device): 94 """Return a dict from process name to list of running PIDs on the device.""" 95 96 # Some custom ROMs use busybox instead of toolbox for ps. Without -w, 97 # busybox truncates the output, and very long package names like 98 # com.exampleisverylongtoolongbyfar.plasma exceed the limit. 99 # 100 # Perform the check for this on the device to avoid an adb roundtrip 101 # Some devices might not have readlink or which, so we need to handle 102 # this as well. 103 # 104 # Gracefully handle [ or readlink being missing by always using `ps` if 105 # readlink is missing. (API 18 has [, but not readlink). 106 107 ps_script = """ 108 if $(ls /system/bin/readlink >/dev/null 2>&1); then 109 if [ $(readlink /system/bin/ps) == "toolbox" ]; then 110 ps; 111 else 112 ps -w; 113 fi 114 else 115 ps; 116 fi 117 """ 118 ps_script = " ".join([line.strip() for line in ps_script.splitlines()]) 119 120 output, _ = device.shell([ps_script]) 121 return parse_ps_output(output) 122 123def parse_ps_output(output): 124 processes = dict() 125 output = adb.split_lines(output.replace("\r", "")) 126 columns = output.pop(0).split() 127 try: 128 pid_column = columns.index("PID") 129 except ValueError: 130 pid_column = 1 131 while output: 132 columns = output.pop().split() 133 process_name = columns[-1] 134 pid = int(columns[pid_column]) 135 if process_name in processes: 136 processes[process_name].append(pid) 137 else: 138 processes[process_name] = [pid] 139 140 return processes 141 142 143def get_pids(device, process_name): 144 processes = get_processes(device) 145 return processes.get(process_name, []) 146 147 148def start_gdbserver(device, gdbserver_local_path, gdbserver_remote_path, 149 target_pid, run_cmd, debug_socket, port, user=None): 150 """Start gdbserver in the background and forward necessary ports. 151 152 Args: 153 device: ADB device to start gdbserver on. 154 gdbserver_local_path: Host path to push gdbserver from, can be None. 155 gdbserver_remote_path: Device path to push gdbserver to. 156 target_pid: PID of device process to attach to. 157 run_cmd: Command to run on the device. 158 debug_socket: Device path to place gdbserver unix domain socket. 159 port: Host port to forward the debug_socket to. 160 user: Device user to run gdbserver as. 161 162 Returns: 163 Popen handle to the `adb shell` process gdbserver was started with. 164 """ 165 166 assert target_pid is None or run_cmd is None 167 168 # Push gdbserver to the target. 169 if gdbserver_local_path is not None: 170 device.push(gdbserver_local_path, gdbserver_remote_path) 171 172 # Run gdbserver. 173 gdbserver_cmd = [gdbserver_remote_path, "--once", 174 "+{}".format(debug_socket)] 175 176 if target_pid is not None: 177 gdbserver_cmd += ["--attach", str(target_pid)] 178 else: 179 gdbserver_cmd += run_cmd 180 181 device.forward("tcp:{}".format(port), 182 "localfilesystem:{}".format(debug_socket)) 183 atexit.register(lambda: device.forward_remove("tcp:{}".format(port))) 184 gdbserver_cmd = get_run_as_cmd(user, gdbserver_cmd) 185 186 gdbserver_output_path = os.path.join(tempfile.gettempdir(), 187 "gdbclient.log") 188 print("Redirecting gdbserver output to {}".format(gdbserver_output_path)) 189 gdbserver_output = file(gdbserver_output_path, 'w') 190 return device.shell_popen(gdbserver_cmd, stdout=gdbserver_output, 191 stderr=gdbserver_output) 192 193 194def find_file(device, executable_path, sysroot, user=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 user: if necessary, the user to download the file as. 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 cmd = get_run_as_cmd(user, 235 ["cat", executable_path, ">", remote_temp_path]) 236 try: 237 device.shell(cmd) 238 except adb.ShellError: 239 raise RuntimeError("Failed to copy '{}' to temporary folder on " 240 "device".format(executable_path)) 241 device.pull(remote_temp_path, local_path) 242 yield (local_path, False) 243 244 for path, found_locally in generate_files(): 245 if os.path.isfile(path): 246 return (open(path, "r"), found_locally) 247 raise RuntimeError('Could not find executable {}'.format(executable_path)) 248 249 250def find_binary(device, pid, sysroot, user=None): 251 """Finds a device executable file corresponding to |pid|.""" 252 return find_file(device, "/proc/{}/exe".format(pid), sysroot, user) 253 254 255def get_binary_arch(binary_file): 256 """Parse a binary's ELF header for arch.""" 257 try: 258 binary_file.seek(0) 259 binary = binary_file.read(0x14) 260 except IOError: 261 raise RuntimeError("failed to read binary file") 262 ei_class = ord(binary[0x4]) # 1 = 32-bit, 2 = 64-bit 263 ei_data = ord(binary[0x5]) # Endianness 264 265 assert ei_class == 1 or ei_class == 2 266 if ei_data != 1: 267 raise RuntimeError("binary isn't little-endian?") 268 269 e_machine = ord(binary[0x13]) << 8 | ord(binary[0x12]) 270 if e_machine == 0x28: 271 assert ei_class == 1 272 return "arm" 273 elif e_machine == 0xB7: 274 assert ei_class == 2 275 return "arm64" 276 elif e_machine == 0x03: 277 assert ei_class == 1 278 return "x86" 279 elif e_machine == 0x3E: 280 assert ei_class == 2 281 return "x86_64" 282 elif e_machine == 0x08: 283 if ei_class == 1: 284 return "mips" 285 else: 286 return "mips64" 287 else: 288 raise RuntimeError("unknown architecture: 0x{:x}".format(e_machine)) 289 290 291def start_gdb(gdb_path, gdb_commands, gdb_flags=None): 292 """Start gdb in the background and block until it finishes. 293 294 Args: 295 gdb_path: Path of the gdb binary. 296 gdb_commands: Contents of GDB script to run. 297 gdb_flags: List of flags to append to gdb command. 298 """ 299 300 # Windows disallows opening the file while it's open for writing. 301 gdb_script_fd, gdb_script_path = tempfile.mkstemp() 302 os.write(gdb_script_fd, gdb_commands) 303 os.close(gdb_script_fd) 304 gdb_args = [gdb_path, "-x", gdb_script_path] + (gdb_flags or []) 305 306 kwargs = {} 307 if sys.platform.startswith("win"): 308 kwargs["creationflags"] = subprocess.CREATE_NEW_CONSOLE 309 310 gdb_process = subprocess.Popen(gdb_args, **kwargs) 311 while gdb_process.returncode is None: 312 try: 313 gdb_process.communicate() 314 except KeyboardInterrupt: 315 pass 316 317 os.unlink(gdb_script_path) 318 319