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 ps_script = """ 105 if [ ! -x /system/bin/readlink -o ! -x /system/bin/which ]; then 106 ps; 107 elif [ $(readlink $(which ps)) == "toolbox" ]; then 108 ps; 109 else 110 ps -w; 111 fi 112 """ 113 ps_script = " ".join([line.strip() for line in ps_script.splitlines()]) 114 115 output, _ = device.shell([ps_script]) 116 117 processes = dict() 118 output = output.replace("\r", "").splitlines() 119 columns = output.pop(0).split() 120 try: 121 pid_column = columns.index("PID") 122 except ValueError: 123 pid_column = 1 124 while output: 125 columns = output.pop().split() 126 process_name = columns[-1] 127 pid = int(columns[pid_column]) 128 if process_name in processes: 129 processes[process_name].append(pid) 130 else: 131 processes[process_name] = [pid] 132 133 return processes 134 135 136def get_pids(device, process_name): 137 processes = get_processes(device) 138 return processes.get(process_name, []) 139 140 141def start_gdbserver(device, gdbserver_local_path, gdbserver_remote_path, 142 target_pid, run_cmd, debug_socket, port, user=None): 143 """Start gdbserver in the background and forward necessary ports. 144 145 Args: 146 device: ADB device to start gdbserver on. 147 gdbserver_local_path: Host path to push gdbserver from, can be None. 148 gdbserver_remote_path: Device path to push gdbserver to. 149 target_pid: PID of device process to attach to. 150 run_cmd: Command to run on the device. 151 debug_socket: Device path to place gdbserver unix domain socket. 152 port: Host port to forward the debug_socket to. 153 user: Device user to run gdbserver as. 154 155 Returns: 156 Popen handle to the `adb shell` process gdbserver was started with. 157 """ 158 159 assert target_pid is None or run_cmd is None 160 161 # Push gdbserver to the target. 162 if gdbserver_local_path is not None: 163 device.push(gdbserver_local_path, gdbserver_remote_path) 164 165 # Run gdbserver. 166 gdbserver_cmd = [gdbserver_remote_path, "--once", 167 "+{}".format(debug_socket)] 168 169 if target_pid is not None: 170 gdbserver_cmd += ["--attach", str(target_pid)] 171 else: 172 gdbserver_cmd += run_cmd 173 174 device.forward("tcp:{}".format(port), 175 "localfilesystem:{}".format(debug_socket)) 176 atexit.register(lambda: device.forward_remove("tcp:{}".format(port))) 177 gdbserver_cmd = get_run_as_cmd(user, gdbserver_cmd) 178 179 # Use ppid so that the file path stays the same. 180 gdbclient_output_path = os.path.join(tempfile.gettempdir(), 181 "gdbclient-{}".format(os.getppid())) 182 print "Redirecting gdbclient output to {}".format(gdbclient_output_path) 183 gdbclient_output = file(gdbclient_output_path, 'w') 184 return device.shell_popen(gdbserver_cmd, stdout=gdbclient_output, 185 stderr=gdbclient_output) 186 187 188def find_file(device, executable_path, sysroot, user=None): 189 """Finds a device executable file. 190 191 This function first attempts to find the local file which will 192 contain debug symbols. If that fails, it will fall back to 193 downloading the stripped file from the device. 194 195 Args: 196 device: the AndroidDevice object to use. 197 executable_path: absolute path to the executable or symlink. 198 sysroot: absolute path to the built symbol sysroot. 199 user: if necessary, the user to download the file as. 200 201 Returns: 202 A tuple containing (<open file object>, <was found locally>). 203 204 Raises: 205 RuntimeError: could not find the executable binary. 206 ValueError: |executable_path| is not absolute. 207 """ 208 if not os.path.isabs(executable_path): 209 raise ValueError("'{}' is not an absolute path".format(executable_path)) 210 211 def generate_files(): 212 """Yields (<file name>, <found locally>) tuples.""" 213 # First look locally to avoid shelling into the device if possible. 214 # os.path.join() doesn't combine absolute paths, use + instead. 215 yield (sysroot + executable_path, True) 216 217 # Next check if the path is a symlink. 218 try: 219 target = device.shell(['readlink', '-e', '-n', executable_path])[0] 220 yield (sysroot + target, True) 221 except adb.ShellError: 222 pass 223 224 # Last, download the stripped executable from the device if necessary. 225 file_name = "gdbclient-binary-{}".format(os.getppid()) 226 remote_temp_path = "/data/local/tmp/{}".format(file_name) 227 local_path = os.path.join(tempfile.gettempdir(), file_name) 228 cmd = get_run_as_cmd(user, 229 ["cat", executable_path, ">", remote_temp_path]) 230 try: 231 device.shell(cmd) 232 except adb.ShellError: 233 raise RuntimeError("Failed to copy '{}' to temporary folder on " 234 "device".format(executable_path)) 235 device.pull(remote_temp_path, local_path) 236 yield (local_path, False) 237 238 for path, found_locally in generate_files(): 239 if os.path.isfile(path): 240 return (open(path, "r"), found_locally) 241 raise RuntimeError('Could not find executable {}'.format(executable_path)) 242 243 244def find_binary(device, pid, sysroot, user=None): 245 """Finds a device executable file corresponding to |pid|.""" 246 return find_file(device, "/proc/{}/exe".format(pid), sysroot, user) 247 248 249def get_binary_arch(binary_file): 250 """Parse a binary's ELF header for arch.""" 251 try: 252 binary_file.seek(0) 253 binary = binary_file.read(0x14) 254 except IOError: 255 raise RuntimeError("failed to read binary file") 256 ei_class = ord(binary[0x4]) # 1 = 32-bit, 2 = 64-bit 257 ei_data = ord(binary[0x5]) # Endianness 258 259 assert ei_class == 1 or ei_class == 2 260 if ei_data != 1: 261 raise RuntimeError("binary isn't little-endian?") 262 263 e_machine = ord(binary[0x13]) << 8 | ord(binary[0x12]) 264 if e_machine == 0x28: 265 assert ei_class == 1 266 return "arm" 267 elif e_machine == 0xB7: 268 assert ei_class == 2 269 return "arm64" 270 elif e_machine == 0x03: 271 assert ei_class == 1 272 return "x86" 273 elif e_machine == 0x3E: 274 assert ei_class == 2 275 return "x86_64" 276 elif e_machine == 0x08: 277 if ei_class == 1: 278 return "mips" 279 else: 280 return "mips64" 281 else: 282 raise RuntimeError("unknown architecture: 0x{:x}".format(e_machine)) 283 284 285def start_gdb(gdb_path, gdb_commands, gdb_flags=None): 286 """Start gdb in the background and block until it finishes. 287 288 Args: 289 gdb_path: Path of the gdb binary. 290 gdb_commands: Contents of GDB script to run. 291 gdb_flags: List of flags to append to gdb command. 292 """ 293 294 with tempfile.NamedTemporaryFile() as gdb_script: 295 gdb_script.write(gdb_commands) 296 gdb_script.flush() 297 gdb_args = [gdb_path, "-x", gdb_script.name] + (gdb_flags or []) 298 gdb_process = subprocess.Popen(gdb_args) 299 while gdb_process.returncode is None: 300 try: 301 gdb_process.communicate() 302 except KeyboardInterrupt: 303 pass 304 305