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