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