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