blob: e3a050330ca809e7fee3195e7847761cd1b02bce [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
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -070023import posixpath
Elliott Hughes89e1ecf2017-06-30 14:03:32 -070024import re
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -070025import shutil
Josh Gao043bad72015-09-22 11:43:08 -070026import subprocess
27import sys
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -070028import tempfile
Alex Light92476652019-01-17 11:18:48 -080029import textwrap
Josh Gao043bad72015-09-22 11:43:08 -070030
31# Shared functions across gdbclient.py and ndk-gdb.py.
32import gdbrunner
33
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -070034g_temp_dirs = []
35
Josh Gao043bad72015-09-22 11:43:08 -070036def get_gdbserver_path(root, arch):
Haibo Huang931cd0b2019-03-26 13:57:32 -070037 path = "{}/prebuilts/misc/gdbserver/android-{}/gdbserver{}"
Josh Gao043bad72015-09-22 11:43:08 -070038 if arch.endswith("64"):
Haibo Huang931cd0b2019-03-26 13:57:32 -070039 return path.format(root, arch, "64")
Josh Gao043bad72015-09-22 11:43:08 -070040 else:
Haibo Huang931cd0b2019-03-26 13:57:32 -070041 return path.format(root, arch, "")
Josh Gao043bad72015-09-22 11:43:08 -070042
43
Elliott Hughes89e1ecf2017-06-30 14:03:32 -070044def get_tracer_pid(device, pid):
45 if pid is None:
46 return 0
47
48 line, _ = device.shell(["grep", "-e", "^TracerPid:", "/proc/{}/status".format(pid)])
49 tracer_pid = re.sub('TracerPid:\t(.*)\n', r'\1', line)
50 return int(tracer_pid)
51
52
Josh Gao043bad72015-09-22 11:43:08 -070053def parse_args():
54 parser = gdbrunner.ArgumentParser()
55
56 group = parser.add_argument_group(title="attach target")
57 group = group.add_mutually_exclusive_group(required=True)
58 group.add_argument(
59 "-p", dest="target_pid", metavar="PID", type=int,
60 help="attach to a process with specified PID")
61 group.add_argument(
62 "-n", dest="target_name", metavar="NAME",
63 help="attach to a process with specified name")
64 group.add_argument(
65 "-r", dest="run_cmd", metavar="CMD", nargs=argparse.REMAINDER,
66 help="run a binary on the device, with args")
67
68 parser.add_argument(
69 "--port", nargs="?", default="5039",
Elliott Hughes89e1ecf2017-06-30 14:03:32 -070070 help="override the port used on the host [default: 5039]")
Josh Gao043bad72015-09-22 11:43:08 -070071 parser.add_argument(
72 "--user", nargs="?", default="root",
73 help="user to run commands as on the device [default: root]")
Alex Light92476652019-01-17 11:18:48 -080074 parser.add_argument(
75 "--setup-forwarding", default=None, choices=["gdb", "vscode"],
76 help=("Setup the gdbserver and port forwarding. Prints commands or " +
77 ".vscode/launch.json configuration needed to connect the debugging " +
78 "client to the server."))
Josh Gao043bad72015-09-22 11:43:08 -070079
Peter Collingbourne63bf1082018-12-19 20:51:42 -080080 parser.add_argument(
81 "--env", nargs=1, action="append", metavar="VAR=VALUE",
82 help="set environment variable when running a binary")
83
Josh Gao043bad72015-09-22 11:43:08 -070084 return parser.parse_args()
85
86
Elliott Hughes1a2f12d2017-06-02 13:15:59 -070087def verify_device(root, device):
Josh Gao466e2892017-07-13 15:39:05 -070088 name = device.get_prop("ro.product.name")
89 target_device = os.environ["TARGET_PRODUCT"]
90 if target_device != name:
91 msg = "TARGET_PRODUCT ({}) does not match attached device ({})"
92 sys.exit(msg.format(target_device, name))
Josh Gao043bad72015-09-22 11:43:08 -070093
94
95def get_remote_pid(device, process_name):
96 processes = gdbrunner.get_processes(device)
97 if process_name not in processes:
98 msg = "failed to find running process {}".format(process_name)
99 sys.exit(msg)
100 pids = processes[process_name]
101 if len(pids) > 1:
102 msg = "multiple processes match '{}': {}".format(process_name, pids)
103 sys.exit(msg)
104
105 # Fetch the binary using the PID later.
106 return pids[0]
107
108
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700109def make_temp_dir(prefix):
110 global g_temp_dirs
111 result = tempfile.mkdtemp(prefix='gdbclient-linker-')
112 g_temp_dirs.append(result)
113 return result
114
115
116def ensure_linker(device, sysroot, interp):
117 """Ensure that the device's linker exists on the host.
118
119 PT_INTERP is usually /system/bin/linker[64], but on the device, that file is
120 a symlink to /apex/com.android.runtime/bin/linker[64]. The symbolized linker
121 binary on the host is located in ${sysroot}/apex, not in ${sysroot}/system,
122 so add the ${sysroot}/apex path to the solib search path.
123
124 PT_INTERP will be /system/bin/bootstrap/linker[64] for executables using the
125 non-APEX/bootstrap linker. No search path modification is needed.
126
127 For a tapas build, only an unbundled app is built, and there is no linker in
128 ${sysroot} at all, so copy the linker from the device.
129
130 Returns:
131 A directory to add to the soinfo search path or None if no directory
132 needs to be added.
133 """
134
135 # Static executables have no interpreter.
136 if interp is None:
137 return None
138
139 # gdb will search for the linker using the PT_INTERP path. First try to find
140 # it in the sysroot.
141 local_path = os.path.join(sysroot, interp.lstrip("/"))
142 if os.path.exists(local_path):
143 return None
144
145 # If the linker on the device is a symlink, search for the symlink's target
146 # in the sysroot directory.
147 interp_real, _ = device.shell(["realpath", interp])
148 interp_real = interp_real.strip()
149 local_path = os.path.join(sysroot, interp_real.lstrip("/"))
150 if os.path.exists(local_path):
151 if posixpath.basename(interp) == posixpath.basename(interp_real):
152 # Add the interpreter's directory to the search path.
153 return os.path.dirname(local_path)
154 else:
155 # If PT_INTERP is linker_asan[64], but the sysroot file is
156 # linker[64], then copy the local file to the name gdb expects.
157 result = make_temp_dir('gdbclient-linker-')
158 shutil.copy(local_path, os.path.join(result, posixpath.basename(interp)))
159 return result
160
161 # Pull the system linker.
162 result = make_temp_dir('gdbclient-linker-')
163 device.pull(interp, os.path.join(result, posixpath.basename(interp)))
164 return result
Josh Gao043bad72015-09-22 11:43:08 -0700165
166
David Pursell639d1c42015-10-20 15:38:32 -0700167def handle_switches(args, sysroot):
Josh Gao043bad72015-09-22 11:43:08 -0700168 """Fetch the targeted binary and determine how to attach gdb.
169
170 Args:
171 args: Parsed arguments.
172 sysroot: Local sysroot path.
173
174 Returns:
175 (binary_file, attach_pid, run_cmd).
176 Precisely one of attach_pid or run_cmd will be None.
177 """
178
179 device = args.device
180 binary_file = None
181 pid = None
182 run_cmd = None
183
Josh Gao057c2732017-05-24 15:55:50 -0700184 args.su_cmd = ["su", args.user] if args.user else []
185
Josh Gao043bad72015-09-22 11:43:08 -0700186 if args.target_pid:
187 # Fetch the binary using the PID later.
188 pid = args.target_pid
189 elif args.target_name:
190 # Fetch the binary using the PID later.
191 pid = get_remote_pid(device, args.target_name)
192 elif args.run_cmd:
193 if not args.run_cmd[0]:
194 sys.exit("empty command passed to -r")
Josh Gao043bad72015-09-22 11:43:08 -0700195 run_cmd = args.run_cmd
Kevin Rocard258c89e2017-07-12 18:21:29 -0700196 if not run_cmd[0].startswith("/"):
197 try:
198 run_cmd[0] = gdbrunner.find_executable_path(device, args.run_cmd[0],
199 run_as_cmd=args.su_cmd)
200 except RuntimeError:
201 sys.exit("Could not find executable '{}' passed to -r, "
202 "please provide an absolute path.".format(args.run_cmd[0]))
203
David Pursell639d1c42015-10-20 15:38:32 -0700204 binary_file, local = gdbrunner.find_file(device, run_cmd[0], sysroot,
Josh Gao057c2732017-05-24 15:55:50 -0700205 run_as_cmd=args.su_cmd)
Josh Gao043bad72015-09-22 11:43:08 -0700206 if binary_file is None:
207 assert pid is not None
208 try:
David Pursell639d1c42015-10-20 15:38:32 -0700209 binary_file, local = gdbrunner.find_binary(device, pid, sysroot,
Josh Gao057c2732017-05-24 15:55:50 -0700210 run_as_cmd=args.su_cmd)
Josh Gao043bad72015-09-22 11:43:08 -0700211 except adb.ShellError:
212 sys.exit("failed to pull binary for PID {}".format(pid))
213
David Pursell639d1c42015-10-20 15:38:32 -0700214 if not local:
215 logging.warning("Couldn't find local unstripped executable in {},"
216 " symbols may not be available.".format(sysroot))
217
Josh Gao043bad72015-09-22 11:43:08 -0700218 return (binary_file, pid, run_cmd)
219
Alex Light92476652019-01-17 11:18:48 -0800220def generate_vscode_script(gdbpath, root, sysroot, binary_name, port, dalvik_gdb_script, solib_search_path):
221 # TODO It would be nice if we didn't need to copy this or run the
222 # gdbclient.py program manually. Doing this would probably require
223 # writing a vscode extension or modifying an existing one.
224 res = {
225 "name": "(gdbclient.py) Attach {} (port: {})".format(binary_name.split("/")[-1], port),
226 "type": "cppdbg",
227 "request": "launch", # Needed for gdbserver.
228 "cwd": root,
229 "program": binary_name,
230 "MIMode": "gdb",
231 "miDebuggerServerAddress": "localhost:{}".format(port),
232 "miDebuggerPath": gdbpath,
233 "setupCommands": [
234 {
235 # Required for vscode.
236 "description": "Enable pretty-printing for gdb",
237 "text": "-enable-pretty-printing",
238 "ignoreFailures": True,
239 },
240 {
241 "description": "gdb command: dir",
242 "text": "-environment-directory {}".format(root),
243 "ignoreFailures": False
244 },
245 {
246 "description": "gdb command: set solib-search-path",
247 "text": "-gdb-set solib-search-path {}".format(":".join(solib_search_path)),
248 "ignoreFailures": False
249 },
250 {
251 "description": "gdb command: set solib-absolute-prefix",
252 "text": "-gdb-set solib-absolute-prefix {}".format(sysroot),
253 "ignoreFailures": False
254 },
255 ]
256 }
257 if dalvik_gdb_script:
258 res["setupCommands"].append({
259 "description": "gdb command: source art commands",
260 "text": "-interpreter-exec console \"source {}\"".format(dalvik_gdb_script),
261 "ignoreFailures": False,
262 })
263 return json.dumps(res, indent=4)
Josh Gao466e2892017-07-13 15:39:05 -0700264
Alex Light92476652019-01-17 11:18:48 -0800265def generate_gdb_script(root, sysroot, binary_name, port, dalvik_gdb_script, solib_search_path, connect_timeout):
Josh Gao043bad72015-09-22 11:43:08 -0700266 solib_search_path = ":".join(solib_search_path)
267
268 gdb_commands = ""
Alex Light92476652019-01-17 11:18:48 -0800269 gdb_commands += "file '{}'\n".format(binary_name)
Josh Gao19f18ce2015-10-22 16:08:13 -0700270 gdb_commands += "directory '{}'\n".format(root)
Josh Gao043bad72015-09-22 11:43:08 -0700271 gdb_commands += "set solib-absolute-prefix {}\n".format(sysroot)
272 gdb_commands += "set solib-search-path {}\n".format(solib_search_path)
Alex Light92476652019-01-17 11:18:48 -0800273 if dalvik_gdb_script:
Josh Gao043bad72015-09-22 11:43:08 -0700274 gdb_commands += "source {}\n".format(dalvik_gdb_script)
275
David Pursell320f8812015-10-05 14:22:10 -0700276 # Try to connect for a few seconds, sometimes the device gdbserver takes
277 # a little bit to come up, especially on emulators.
278 gdb_commands += """
279python
280
281def target_remote_with_retry(target, timeout_seconds):
282 import time
283 end_time = time.time() + timeout_seconds
284 while True:
285 try:
Dan Albertd124bc72018-06-26 11:15:16 -0700286 gdb.execute("target extended-remote " + target)
David Pursell320f8812015-10-05 14:22:10 -0700287 return True
288 except gdb.error as e:
289 time_left = end_time - time.time()
290 if time_left < 0 or time_left > timeout_seconds:
291 print("Error: unable to connect to device.")
292 print(e)
293 return False
294 time.sleep(min(0.25, time_left))
295
296target_remote_with_retry(':{}', {})
297
298end
299""".format(port, connect_timeout)
300
Josh Gao043bad72015-09-22 11:43:08 -0700301 return gdb_commands
302
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700303def generate_setup_script(gdbpath, sysroot, linker_search_dir, binary_file, is64bit, port, debugger, connect_timeout=5):
Alex Light92476652019-01-17 11:18:48 -0800304 # Generate a setup script.
305 # TODO: Detect the zygote and run 'art-on' automatically.
306 root = os.environ["ANDROID_BUILD_TOP"]
307 symbols_dir = os.path.join(sysroot, "system", "lib64" if is64bit else "lib")
308 vendor_dir = os.path.join(sysroot, "vendor", "lib64" if is64bit else "lib")
309
310 solib_search_path = []
311 symbols_paths = ["", "hw", "ssl/engines", "drm", "egl", "soundfx"]
312 vendor_paths = ["", "hw", "egl"]
313 solib_search_path += [os.path.join(symbols_dir, x) for x in symbols_paths]
314 solib_search_path += [os.path.join(vendor_dir, x) for x in vendor_paths]
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700315 if linker_search_dir is not None:
316 solib_search_path += [linker_search_dir]
Alex Light92476652019-01-17 11:18:48 -0800317
318 dalvik_gdb_script = os.path.join(root, "development", "scripts", "gdb", "dalvik.gdb")
319 if not os.path.exists(dalvik_gdb_script):
320 logging.warning(("couldn't find {} - ART debugging options will not " +
321 "be available").format(dalvik_gdb_script))
322 dalvik_gdb_script = None
323
324 if debugger == "vscode":
325 return generate_vscode_script(
326 gdbpath, root, sysroot, binary_file.name, port, dalvik_gdb_script, solib_search_path)
327 elif debugger == "gdb":
328 return generate_gdb_script(root, sysroot, binary_file.name, port, dalvik_gdb_script, solib_search_path, connect_timeout)
329 else:
330 raise Exception("Unknown debugger type " + debugger)
331
Josh Gao043bad72015-09-22 11:43:08 -0700332
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700333def do_main():
Josh Gao466e2892017-07-13 15:39:05 -0700334 required_env = ["ANDROID_BUILD_TOP",
335 "ANDROID_PRODUCT_OUT", "TARGET_PRODUCT"]
336 for env in required_env:
337 if env not in os.environ:
338 sys.exit(
339 "Environment variable '{}' not defined, have you run lunch?".format(env))
340
Josh Gao043bad72015-09-22 11:43:08 -0700341 args = parse_args()
342 device = args.device
Josh Gao44b84a82015-10-28 11:57:37 -0700343
344 if device is None:
345 sys.exit("ERROR: Failed to find device.")
346
Josh Gao043bad72015-09-22 11:43:08 -0700347 root = os.environ["ANDROID_BUILD_TOP"]
Josh Gao466e2892017-07-13 15:39:05 -0700348 sysroot = os.path.join(os.environ["ANDROID_PRODUCT_OUT"], "symbols")
Josh Gao043bad72015-09-22 11:43:08 -0700349
350 # Make sure the environment matches the attached device.
Elliott Hughes1a2f12d2017-06-02 13:15:59 -0700351 verify_device(root, device)
Josh Gao043bad72015-09-22 11:43:08 -0700352
353 debug_socket = "/data/local/tmp/debug_socket"
354 pid = None
355 run_cmd = None
356
357 # Fetch binary for -p, -n.
David Pursell639d1c42015-10-20 15:38:32 -0700358 binary_file, pid, run_cmd = handle_switches(args, sysroot)
Josh Gao043bad72015-09-22 11:43:08 -0700359
360 with binary_file:
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700361 if sys.platform.startswith("linux"):
362 platform_name = "linux-x86"
363 elif sys.platform.startswith("darwin"):
364 platform_name = "darwin-x86"
365 else:
366 sys.exit("Unknown platform: {}".format(sys.platform))
367
Josh Gao043bad72015-09-22 11:43:08 -0700368 arch = gdbrunner.get_binary_arch(binary_file)
369 is64bit = arch.endswith("64")
370
371 # Make sure we have the linker
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700372 llvm_readobj_path = os.path.join(root, "prebuilts", "clang", "host", platform_name,
373 "llvm-binutils-stable", "llvm-readobj")
374 interp = gdbrunner.get_binary_interp(binary_file.name, llvm_readobj_path)
375 linker_search_dir = ensure_linker(device, sysroot, interp)
Josh Gao043bad72015-09-22 11:43:08 -0700376
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700377 tracer_pid = get_tracer_pid(device, pid)
378 if tracer_pid == 0:
Peter Collingbourne63bf1082018-12-19 20:51:42 -0800379 cmd_prefix = args.su_cmd
380 if args.env:
381 cmd_prefix += ['env'] + [v[0] for v in args.env]
382
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700383 # Start gdbserver.
384 gdbserver_local_path = get_gdbserver_path(root, arch)
385 gdbserver_remote_path = "/data/local/tmp/{}-gdbserver".format(arch)
386 gdbrunner.start_gdbserver(
387 device, gdbserver_local_path, gdbserver_remote_path,
388 target_pid=pid, run_cmd=run_cmd, debug_socket=debug_socket,
Peter Collingbourne63bf1082018-12-19 20:51:42 -0800389 port=args.port, run_as_cmd=cmd_prefix)
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700390 else:
391 print "Connecting to tracing pid {} using local port {}".format(tracer_pid, args.port)
392 gdbrunner.forward_gdbserver_port(device, local=args.port,
393 remote="tcp:{}".format(args.port))
Josh Gao043bad72015-09-22 11:43:08 -0700394
Josh Gao043bad72015-09-22 11:43:08 -0700395 gdb_path = os.path.join(root, "prebuilts", "gdb", platform_name, "bin",
396 "gdb")
Alex Light92476652019-01-17 11:18:48 -0800397 # Generate a gdb script.
398 setup_commands = generate_setup_script(gdbpath=gdb_path,
399 sysroot=sysroot,
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700400 linker_search_dir=linker_search_dir,
Alex Light92476652019-01-17 11:18:48 -0800401 binary_file=binary_file,
402 is64bit=is64bit,
403 port=args.port,
404 debugger=args.setup_forwarding or "gdb")
Josh Gao043bad72015-09-22 11:43:08 -0700405
Alex Light92476652019-01-17 11:18:48 -0800406 if not args.setup_forwarding:
407 # Print a newline to separate our messages from the GDB session.
408 print("")
David Pursell639d1c42015-10-20 15:38:32 -0700409
Alex Light92476652019-01-17 11:18:48 -0800410 # Start gdb.
411 gdbrunner.start_gdb(gdb_path, setup_commands)
412 else:
413 print("")
414 print setup_commands
415 print("")
416 if args.setup_forwarding == "vscode":
417 print textwrap.dedent("""
418 Paste the above json into .vscode/launch.json and start the debugger as
419 normal. Press enter in this terminal once debugging is finished to shutdown
420 the gdbserver and close all the ports.""")
421 else:
422 print textwrap.dedent("""
423 Paste the above gdb commands into the gdb frontend to setup the gdbserver
424 connection. Press enter in this terminal once debugging is finished to
425 shutdown the gdbserver and close all the ports.""")
426 print("")
427 raw_input("Press enter to shutdown gdbserver")
Josh Gao043bad72015-09-22 11:43:08 -0700428
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700429
430def main():
431 try:
432 do_main()
433 finally:
434 global g_temp_dirs
435 for temp_dir in g_temp_dirs:
436 shutil.rmtree(temp_dir)
437
438
Josh Gao043bad72015-09-22 11:43:08 -0700439if __name__ == "__main__":
440 main()