blob: 611b2e89be900bea4084c902aa448e558680450c [file] [log] [blame]
Josh Gao043bad72015-09-22 11:43:08 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2015 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import adb
19import argparse
Alex Light92476652019-01-17 11:18:48 -080020import json
Josh Gao043bad72015-09-22 11:43:08 -070021import logging
22import os
Elliott Hughes89e1ecf2017-06-30 14:03:32 -070023import re
Josh Gao043bad72015-09-22 11:43:08 -070024import subprocess
25import sys
Alex Light92476652019-01-17 11:18:48 -080026import textwrap
Josh Gao043bad72015-09-22 11:43:08 -070027
28# Shared functions across gdbclient.py and ndk-gdb.py.
29import gdbrunner
30
31def get_gdbserver_path(root, arch):
32 path = "{}/prebuilts/misc/android-{}/gdbserver{}/gdbserver{}"
33 if arch.endswith("64"):
34 return path.format(root, arch, "64", "64")
35 else:
36 return path.format(root, arch, "", "")
37
38
Elliott Hughes89e1ecf2017-06-30 14:03:32 -070039def get_tracer_pid(device, pid):
40 if pid is None:
41 return 0
42
43 line, _ = device.shell(["grep", "-e", "^TracerPid:", "/proc/{}/status".format(pid)])
44 tracer_pid = re.sub('TracerPid:\t(.*)\n', r'\1', line)
45 return int(tracer_pid)
46
47
Josh Gao043bad72015-09-22 11:43:08 -070048def parse_args():
49 parser = gdbrunner.ArgumentParser()
50
51 group = parser.add_argument_group(title="attach target")
52 group = group.add_mutually_exclusive_group(required=True)
53 group.add_argument(
54 "-p", dest="target_pid", metavar="PID", type=int,
55 help="attach to a process with specified PID")
56 group.add_argument(
57 "-n", dest="target_name", metavar="NAME",
58 help="attach to a process with specified name")
59 group.add_argument(
60 "-r", dest="run_cmd", metavar="CMD", nargs=argparse.REMAINDER,
61 help="run a binary on the device, with args")
62
63 parser.add_argument(
64 "--port", nargs="?", default="5039",
Elliott Hughes89e1ecf2017-06-30 14:03:32 -070065 help="override the port used on the host [default: 5039]")
Josh Gao043bad72015-09-22 11:43:08 -070066 parser.add_argument(
67 "--user", nargs="?", default="root",
68 help="user to run commands as on the device [default: root]")
Alex Light92476652019-01-17 11:18:48 -080069 parser.add_argument(
70 "--setup-forwarding", default=None, choices=["gdb", "vscode"],
71 help=("Setup the gdbserver and port forwarding. Prints commands or " +
72 ".vscode/launch.json configuration needed to connect the debugging " +
73 "client to the server."))
Josh Gao043bad72015-09-22 11:43:08 -070074
75 return parser.parse_args()
76
77
Elliott Hughes1a2f12d2017-06-02 13:15:59 -070078def verify_device(root, device):
Josh Gao466e2892017-07-13 15:39:05 -070079 name = device.get_prop("ro.product.name")
80 target_device = os.environ["TARGET_PRODUCT"]
81 if target_device != name:
82 msg = "TARGET_PRODUCT ({}) does not match attached device ({})"
83 sys.exit(msg.format(target_device, name))
Josh Gao043bad72015-09-22 11:43:08 -070084
85
86def get_remote_pid(device, process_name):
87 processes = gdbrunner.get_processes(device)
88 if process_name not in processes:
89 msg = "failed to find running process {}".format(process_name)
90 sys.exit(msg)
91 pids = processes[process_name]
92 if len(pids) > 1:
93 msg = "multiple processes match '{}': {}".format(process_name, pids)
94 sys.exit(msg)
95
96 # Fetch the binary using the PID later.
97 return pids[0]
98
99
100def ensure_linker(device, sysroot, is64bit):
101 local_path = os.path.join(sysroot, "system", "bin", "linker")
102 remote_path = "/system/bin/linker"
103 if is64bit:
104 local_path += "64"
105 remote_path += "64"
106 if not os.path.exists(local_path):
107 device.pull(remote_path, local_path)
108
109
David Pursell639d1c42015-10-20 15:38:32 -0700110def handle_switches(args, sysroot):
Josh Gao043bad72015-09-22 11:43:08 -0700111 """Fetch the targeted binary and determine how to attach gdb.
112
113 Args:
114 args: Parsed arguments.
115 sysroot: Local sysroot path.
116
117 Returns:
118 (binary_file, attach_pid, run_cmd).
119 Precisely one of attach_pid or run_cmd will be None.
120 """
121
122 device = args.device
123 binary_file = None
124 pid = None
125 run_cmd = None
126
Josh Gao057c2732017-05-24 15:55:50 -0700127 args.su_cmd = ["su", args.user] if args.user else []
128
Josh Gao043bad72015-09-22 11:43:08 -0700129 if args.target_pid:
130 # Fetch the binary using the PID later.
131 pid = args.target_pid
132 elif args.target_name:
133 # Fetch the binary using the PID later.
134 pid = get_remote_pid(device, args.target_name)
135 elif args.run_cmd:
136 if not args.run_cmd[0]:
137 sys.exit("empty command passed to -r")
Josh Gao043bad72015-09-22 11:43:08 -0700138 run_cmd = args.run_cmd
Kevin Rocard258c89e2017-07-12 18:21:29 -0700139 if not run_cmd[0].startswith("/"):
140 try:
141 run_cmd[0] = gdbrunner.find_executable_path(device, args.run_cmd[0],
142 run_as_cmd=args.su_cmd)
143 except RuntimeError:
144 sys.exit("Could not find executable '{}' passed to -r, "
145 "please provide an absolute path.".format(args.run_cmd[0]))
146
David Pursell639d1c42015-10-20 15:38:32 -0700147 binary_file, local = gdbrunner.find_file(device, run_cmd[0], sysroot,
Josh Gao057c2732017-05-24 15:55:50 -0700148 run_as_cmd=args.su_cmd)
Josh Gao043bad72015-09-22 11:43:08 -0700149 if binary_file is None:
150 assert pid is not None
151 try:
David Pursell639d1c42015-10-20 15:38:32 -0700152 binary_file, local = gdbrunner.find_binary(device, pid, sysroot,
Josh Gao057c2732017-05-24 15:55:50 -0700153 run_as_cmd=args.su_cmd)
Josh Gao043bad72015-09-22 11:43:08 -0700154 except adb.ShellError:
155 sys.exit("failed to pull binary for PID {}".format(pid))
156
David Pursell639d1c42015-10-20 15:38:32 -0700157 if not local:
158 logging.warning("Couldn't find local unstripped executable in {},"
159 " symbols may not be available.".format(sysroot))
160
Josh Gao043bad72015-09-22 11:43:08 -0700161 return (binary_file, pid, run_cmd)
162
Alex Light92476652019-01-17 11:18:48 -0800163def generate_vscode_script(gdbpath, root, sysroot, binary_name, port, dalvik_gdb_script, solib_search_path):
164 # TODO It would be nice if we didn't need to copy this or run the
165 # gdbclient.py program manually. Doing this would probably require
166 # writing a vscode extension or modifying an existing one.
167 res = {
168 "name": "(gdbclient.py) Attach {} (port: {})".format(binary_name.split("/")[-1], port),
169 "type": "cppdbg",
170 "request": "launch", # Needed for gdbserver.
171 "cwd": root,
172 "program": binary_name,
173 "MIMode": "gdb",
174 "miDebuggerServerAddress": "localhost:{}".format(port),
175 "miDebuggerPath": gdbpath,
176 "setupCommands": [
177 {
178 # Required for vscode.
179 "description": "Enable pretty-printing for gdb",
180 "text": "-enable-pretty-printing",
181 "ignoreFailures": True,
182 },
183 {
184 "description": "gdb command: dir",
185 "text": "-environment-directory {}".format(root),
186 "ignoreFailures": False
187 },
188 {
189 "description": "gdb command: set solib-search-path",
190 "text": "-gdb-set solib-search-path {}".format(":".join(solib_search_path)),
191 "ignoreFailures": False
192 },
193 {
194 "description": "gdb command: set solib-absolute-prefix",
195 "text": "-gdb-set solib-absolute-prefix {}".format(sysroot),
196 "ignoreFailures": False
197 },
198 ]
199 }
200 if dalvik_gdb_script:
201 res["setupCommands"].append({
202 "description": "gdb command: source art commands",
203 "text": "-interpreter-exec console \"source {}\"".format(dalvik_gdb_script),
204 "ignoreFailures": False,
205 })
206 return json.dumps(res, indent=4)
Josh Gao466e2892017-07-13 15:39:05 -0700207
Alex Light92476652019-01-17 11:18:48 -0800208def generate_gdb_script(root, sysroot, binary_name, port, dalvik_gdb_script, solib_search_path, connect_timeout):
Josh Gao043bad72015-09-22 11:43:08 -0700209 solib_search_path = ":".join(solib_search_path)
210
211 gdb_commands = ""
Alex Light92476652019-01-17 11:18:48 -0800212 gdb_commands += "file '{}'\n".format(binary_name)
Josh Gao19f18ce2015-10-22 16:08:13 -0700213 gdb_commands += "directory '{}'\n".format(root)
Josh Gao043bad72015-09-22 11:43:08 -0700214 gdb_commands += "set solib-absolute-prefix {}\n".format(sysroot)
215 gdb_commands += "set solib-search-path {}\n".format(solib_search_path)
Alex Light92476652019-01-17 11:18:48 -0800216 if dalvik_gdb_script:
Josh Gao043bad72015-09-22 11:43:08 -0700217 gdb_commands += "source {}\n".format(dalvik_gdb_script)
218
David Pursell320f8812015-10-05 14:22:10 -0700219 # Try to connect for a few seconds, sometimes the device gdbserver takes
220 # a little bit to come up, especially on emulators.
221 gdb_commands += """
222python
223
224def target_remote_with_retry(target, timeout_seconds):
225 import time
226 end_time = time.time() + timeout_seconds
227 while True:
228 try:
Dan Albertd124bc72018-06-26 11:15:16 -0700229 gdb.execute("target extended-remote " + target)
David Pursell320f8812015-10-05 14:22:10 -0700230 return True
231 except gdb.error as e:
232 time_left = end_time - time.time()
233 if time_left < 0 or time_left > timeout_seconds:
234 print("Error: unable to connect to device.")
235 print(e)
236 return False
237 time.sleep(min(0.25, time_left))
238
239target_remote_with_retry(':{}', {})
240
241end
242""".format(port, connect_timeout)
243
Josh Gao043bad72015-09-22 11:43:08 -0700244 return gdb_commands
245
Alex Light92476652019-01-17 11:18:48 -0800246def generate_setup_script(gdbpath, sysroot, binary_file, is64bit, port, debugger, connect_timeout=5):
247 # Generate a setup script.
248 # TODO: Detect the zygote and run 'art-on' automatically.
249 root = os.environ["ANDROID_BUILD_TOP"]
250 symbols_dir = os.path.join(sysroot, "system", "lib64" if is64bit else "lib")
251 vendor_dir = os.path.join(sysroot, "vendor", "lib64" if is64bit else "lib")
252
253 solib_search_path = []
254 symbols_paths = ["", "hw", "ssl/engines", "drm", "egl", "soundfx"]
255 vendor_paths = ["", "hw", "egl"]
256 solib_search_path += [os.path.join(symbols_dir, x) for x in symbols_paths]
257 solib_search_path += [os.path.join(vendor_dir, x) for x in vendor_paths]
258
259 dalvik_gdb_script = os.path.join(root, "development", "scripts", "gdb", "dalvik.gdb")
260 if not os.path.exists(dalvik_gdb_script):
261 logging.warning(("couldn't find {} - ART debugging options will not " +
262 "be available").format(dalvik_gdb_script))
263 dalvik_gdb_script = None
264
265 if debugger == "vscode":
266 return generate_vscode_script(
267 gdbpath, root, sysroot, binary_file.name, port, dalvik_gdb_script, solib_search_path)
268 elif debugger == "gdb":
269 return generate_gdb_script(root, sysroot, binary_file.name, port, dalvik_gdb_script, solib_search_path, connect_timeout)
270 else:
271 raise Exception("Unknown debugger type " + debugger)
272
Josh Gao043bad72015-09-22 11:43:08 -0700273
274def main():
Josh Gao466e2892017-07-13 15:39:05 -0700275 required_env = ["ANDROID_BUILD_TOP",
276 "ANDROID_PRODUCT_OUT", "TARGET_PRODUCT"]
277 for env in required_env:
278 if env not in os.environ:
279 sys.exit(
280 "Environment variable '{}' not defined, have you run lunch?".format(env))
281
Josh Gao043bad72015-09-22 11:43:08 -0700282 args = parse_args()
283 device = args.device
Josh Gao44b84a82015-10-28 11:57:37 -0700284
285 if device is None:
286 sys.exit("ERROR: Failed to find device.")
287
Josh Gao043bad72015-09-22 11:43:08 -0700288 root = os.environ["ANDROID_BUILD_TOP"]
Josh Gao466e2892017-07-13 15:39:05 -0700289 sysroot = os.path.join(os.environ["ANDROID_PRODUCT_OUT"], "symbols")
Josh Gao043bad72015-09-22 11:43:08 -0700290
291 # Make sure the environment matches the attached device.
Elliott Hughes1a2f12d2017-06-02 13:15:59 -0700292 verify_device(root, device)
Josh Gao043bad72015-09-22 11:43:08 -0700293
294 debug_socket = "/data/local/tmp/debug_socket"
295 pid = None
296 run_cmd = None
297
298 # Fetch binary for -p, -n.
David Pursell639d1c42015-10-20 15:38:32 -0700299 binary_file, pid, run_cmd = handle_switches(args, sysroot)
Josh Gao043bad72015-09-22 11:43:08 -0700300
301 with binary_file:
302 arch = gdbrunner.get_binary_arch(binary_file)
303 is64bit = arch.endswith("64")
304
305 # Make sure we have the linker
306 ensure_linker(device, sysroot, is64bit)
307
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700308 tracer_pid = get_tracer_pid(device, pid)
309 if tracer_pid == 0:
310 # Start gdbserver.
311 gdbserver_local_path = get_gdbserver_path(root, arch)
312 gdbserver_remote_path = "/data/local/tmp/{}-gdbserver".format(arch)
313 gdbrunner.start_gdbserver(
314 device, gdbserver_local_path, gdbserver_remote_path,
315 target_pid=pid, run_cmd=run_cmd, debug_socket=debug_socket,
316 port=args.port, run_as_cmd=args.su_cmd)
317 else:
318 print "Connecting to tracing pid {} using local port {}".format(tracer_pid, args.port)
319 gdbrunner.forward_gdbserver_port(device, local=args.port,
320 remote="tcp:{}".format(args.port))
Josh Gao043bad72015-09-22 11:43:08 -0700321
Josh Gao043bad72015-09-22 11:43:08 -0700322 # Find where gdb is
323 if sys.platform.startswith("linux"):
324 platform_name = "linux-x86"
325 elif sys.platform.startswith("darwin"):
326 platform_name = "darwin-x86"
327 else:
328 sys.exit("Unknown platform: {}".format(sys.platform))
Alex Light92476652019-01-17 11:18:48 -0800329
Josh Gao043bad72015-09-22 11:43:08 -0700330 gdb_path = os.path.join(root, "prebuilts", "gdb", platform_name, "bin",
331 "gdb")
Alex Light92476652019-01-17 11:18:48 -0800332 # Generate a gdb script.
333 setup_commands = generate_setup_script(gdbpath=gdb_path,
334 sysroot=sysroot,
335 binary_file=binary_file,
336 is64bit=is64bit,
337 port=args.port,
338 debugger=args.setup_forwarding or "gdb")
Josh Gao043bad72015-09-22 11:43:08 -0700339
Alex Light92476652019-01-17 11:18:48 -0800340 if not args.setup_forwarding:
341 # Print a newline to separate our messages from the GDB session.
342 print("")
David Pursell639d1c42015-10-20 15:38:32 -0700343
Alex Light92476652019-01-17 11:18:48 -0800344 # Start gdb.
345 gdbrunner.start_gdb(gdb_path, setup_commands)
346 else:
347 print("")
348 print setup_commands
349 print("")
350 if args.setup_forwarding == "vscode":
351 print textwrap.dedent("""
352 Paste the above json into .vscode/launch.json and start the debugger as
353 normal. Press enter in this terminal once debugging is finished to shutdown
354 the gdbserver and close all the ports.""")
355 else:
356 print textwrap.dedent("""
357 Paste the above gdb commands into the gdb frontend to setup the gdbserver
358 connection. Press enter in this terminal once debugging is finished to
359 shutdown the gdbserver and close all the ports.""")
360 print("")
361 raw_input("Press enter to shutdown gdbserver")
Josh Gao043bad72015-09-22 11:43:08 -0700362
363if __name__ == "__main__":
364 main()