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