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