blob: a96aaa885054cade568b0fa327ce749ff196c4b4 [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):
Haibo Huang931cd0b2019-03-26 13:57:32 -070032 path = "{}/prebuilts/misc/gdbserver/android-{}/gdbserver{}"
Josh Gao043bad72015-09-22 11:43:08 -070033 if arch.endswith("64"):
Haibo Huang931cd0b2019-03-26 13:57:32 -070034 return path.format(root, arch, "64")
Josh Gao043bad72015-09-22 11:43:08 -070035 else:
Haibo Huang931cd0b2019-03-26 13:57:32 -070036 return path.format(root, arch, "")
Josh Gao043bad72015-09-22 11:43:08 -070037
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
Peter Collingbourne63bf1082018-12-19 20:51:42 -080075 parser.add_argument(
76 "--env", nargs=1, action="append", metavar="VAR=VALUE",
77 help="set environment variable when running a binary")
78
Josh Gao043bad72015-09-22 11:43:08 -070079 return parser.parse_args()
80
81
Elliott Hughes1a2f12d2017-06-02 13:15:59 -070082def verify_device(root, device):
Josh Gao466e2892017-07-13 15:39:05 -070083 name = device.get_prop("ro.product.name")
84 target_device = os.environ["TARGET_PRODUCT"]
85 if target_device != name:
86 msg = "TARGET_PRODUCT ({}) does not match attached device ({})"
87 sys.exit(msg.format(target_device, name))
Josh Gao043bad72015-09-22 11:43:08 -070088
89
90def get_remote_pid(device, process_name):
91 processes = gdbrunner.get_processes(device)
92 if process_name not in processes:
93 msg = "failed to find running process {}".format(process_name)
94 sys.exit(msg)
95 pids = processes[process_name]
96 if len(pids) > 1:
97 msg = "multiple processes match '{}': {}".format(process_name, pids)
98 sys.exit(msg)
99
100 # Fetch the binary using the PID later.
101 return pids[0]
102
103
104def ensure_linker(device, sysroot, is64bit):
105 local_path = os.path.join(sysroot, "system", "bin", "linker")
106 remote_path = "/system/bin/linker"
107 if is64bit:
108 local_path += "64"
109 remote_path += "64"
110 if not os.path.exists(local_path):
111 device.pull(remote_path, local_path)
112
113
David Pursell639d1c42015-10-20 15:38:32 -0700114def handle_switches(args, sysroot):
Josh Gao043bad72015-09-22 11:43:08 -0700115 """Fetch the targeted binary and determine how to attach gdb.
116
117 Args:
118 args: Parsed arguments.
119 sysroot: Local sysroot path.
120
121 Returns:
122 (binary_file, attach_pid, run_cmd).
123 Precisely one of attach_pid or run_cmd will be None.
124 """
125
126 device = args.device
127 binary_file = None
128 pid = None
129 run_cmd = None
130
Josh Gao057c2732017-05-24 15:55:50 -0700131 args.su_cmd = ["su", args.user] if args.user else []
132
Josh Gao043bad72015-09-22 11:43:08 -0700133 if args.target_pid:
134 # Fetch the binary using the PID later.
135 pid = args.target_pid
136 elif args.target_name:
137 # Fetch the binary using the PID later.
138 pid = get_remote_pid(device, args.target_name)
139 elif args.run_cmd:
140 if not args.run_cmd[0]:
141 sys.exit("empty command passed to -r")
Josh Gao043bad72015-09-22 11:43:08 -0700142 run_cmd = args.run_cmd
Kevin Rocard258c89e2017-07-12 18:21:29 -0700143 if not run_cmd[0].startswith("/"):
144 try:
145 run_cmd[0] = gdbrunner.find_executable_path(device, args.run_cmd[0],
146 run_as_cmd=args.su_cmd)
147 except RuntimeError:
148 sys.exit("Could not find executable '{}' passed to -r, "
149 "please provide an absolute path.".format(args.run_cmd[0]))
150
David Pursell639d1c42015-10-20 15:38:32 -0700151 binary_file, local = gdbrunner.find_file(device, run_cmd[0], sysroot,
Josh Gao057c2732017-05-24 15:55:50 -0700152 run_as_cmd=args.su_cmd)
Josh Gao043bad72015-09-22 11:43:08 -0700153 if binary_file is None:
154 assert pid is not None
155 try:
David Pursell639d1c42015-10-20 15:38:32 -0700156 binary_file, local = gdbrunner.find_binary(device, pid, sysroot,
Josh Gao057c2732017-05-24 15:55:50 -0700157 run_as_cmd=args.su_cmd)
Josh Gao043bad72015-09-22 11:43:08 -0700158 except adb.ShellError:
159 sys.exit("failed to pull binary for PID {}".format(pid))
160
David Pursell639d1c42015-10-20 15:38:32 -0700161 if not local:
162 logging.warning("Couldn't find local unstripped executable in {},"
163 " symbols may not be available.".format(sysroot))
164
Josh Gao043bad72015-09-22 11:43:08 -0700165 return (binary_file, pid, run_cmd)
166
Alex Light92476652019-01-17 11:18:48 -0800167def generate_vscode_script(gdbpath, root, sysroot, binary_name, port, dalvik_gdb_script, solib_search_path):
168 # TODO It would be nice if we didn't need to copy this or run the
169 # gdbclient.py program manually. Doing this would probably require
170 # writing a vscode extension or modifying an existing one.
171 res = {
172 "name": "(gdbclient.py) Attach {} (port: {})".format(binary_name.split("/")[-1], port),
173 "type": "cppdbg",
174 "request": "launch", # Needed for gdbserver.
175 "cwd": root,
176 "program": binary_name,
177 "MIMode": "gdb",
178 "miDebuggerServerAddress": "localhost:{}".format(port),
179 "miDebuggerPath": gdbpath,
180 "setupCommands": [
181 {
182 # Required for vscode.
183 "description": "Enable pretty-printing for gdb",
184 "text": "-enable-pretty-printing",
185 "ignoreFailures": True,
186 },
187 {
188 "description": "gdb command: dir",
189 "text": "-environment-directory {}".format(root),
190 "ignoreFailures": False
191 },
192 {
193 "description": "gdb command: set solib-search-path",
194 "text": "-gdb-set solib-search-path {}".format(":".join(solib_search_path)),
195 "ignoreFailures": False
196 },
197 {
198 "description": "gdb command: set solib-absolute-prefix",
199 "text": "-gdb-set solib-absolute-prefix {}".format(sysroot),
200 "ignoreFailures": False
201 },
202 ]
203 }
204 if dalvik_gdb_script:
205 res["setupCommands"].append({
206 "description": "gdb command: source art commands",
207 "text": "-interpreter-exec console \"source {}\"".format(dalvik_gdb_script),
208 "ignoreFailures": False,
209 })
210 return json.dumps(res, indent=4)
Josh Gao466e2892017-07-13 15:39:05 -0700211
Alex Light92476652019-01-17 11:18:48 -0800212def generate_gdb_script(root, sysroot, binary_name, port, dalvik_gdb_script, solib_search_path, connect_timeout):
Josh Gao043bad72015-09-22 11:43:08 -0700213 solib_search_path = ":".join(solib_search_path)
214
215 gdb_commands = ""
Alex Light92476652019-01-17 11:18:48 -0800216 gdb_commands += "file '{}'\n".format(binary_name)
Josh Gao19f18ce2015-10-22 16:08:13 -0700217 gdb_commands += "directory '{}'\n".format(root)
Josh Gao043bad72015-09-22 11:43:08 -0700218 gdb_commands += "set solib-absolute-prefix {}\n".format(sysroot)
219 gdb_commands += "set solib-search-path {}\n".format(solib_search_path)
Alex Light92476652019-01-17 11:18:48 -0800220 if dalvik_gdb_script:
Josh Gao043bad72015-09-22 11:43:08 -0700221 gdb_commands += "source {}\n".format(dalvik_gdb_script)
222
David Pursell320f8812015-10-05 14:22:10 -0700223 # Try to connect for a few seconds, sometimes the device gdbserver takes
224 # a little bit to come up, especially on emulators.
225 gdb_commands += """
226python
227
228def target_remote_with_retry(target, timeout_seconds):
229 import time
230 end_time = time.time() + timeout_seconds
231 while True:
232 try:
Dan Albertd124bc72018-06-26 11:15:16 -0700233 gdb.execute("target extended-remote " + target)
David Pursell320f8812015-10-05 14:22:10 -0700234 return True
235 except gdb.error as e:
236 time_left = end_time - time.time()
237 if time_left < 0 or time_left > timeout_seconds:
238 print("Error: unable to connect to device.")
239 print(e)
240 return False
241 time.sleep(min(0.25, time_left))
242
243target_remote_with_retry(':{}', {})
244
245end
246""".format(port, connect_timeout)
247
Josh Gao043bad72015-09-22 11:43:08 -0700248 return gdb_commands
249
Alex Light92476652019-01-17 11:18:48 -0800250def generate_setup_script(gdbpath, sysroot, binary_file, is64bit, port, debugger, connect_timeout=5):
251 # Generate a setup script.
252 # TODO: Detect the zygote and run 'art-on' automatically.
253 root = os.environ["ANDROID_BUILD_TOP"]
254 symbols_dir = os.path.join(sysroot, "system", "lib64" if is64bit else "lib")
255 vendor_dir = os.path.join(sysroot, "vendor", "lib64" if is64bit else "lib")
256
257 solib_search_path = []
258 symbols_paths = ["", "hw", "ssl/engines", "drm", "egl", "soundfx"]
259 vendor_paths = ["", "hw", "egl"]
260 solib_search_path += [os.path.join(symbols_dir, x) for x in symbols_paths]
261 solib_search_path += [os.path.join(vendor_dir, x) for x in vendor_paths]
262
263 dalvik_gdb_script = os.path.join(root, "development", "scripts", "gdb", "dalvik.gdb")
264 if not os.path.exists(dalvik_gdb_script):
265 logging.warning(("couldn't find {} - ART debugging options will not " +
266 "be available").format(dalvik_gdb_script))
267 dalvik_gdb_script = None
268
269 if debugger == "vscode":
270 return generate_vscode_script(
271 gdbpath, root, sysroot, binary_file.name, port, dalvik_gdb_script, solib_search_path)
272 elif debugger == "gdb":
273 return generate_gdb_script(root, sysroot, binary_file.name, port, dalvik_gdb_script, solib_search_path, connect_timeout)
274 else:
275 raise Exception("Unknown debugger type " + debugger)
276
Josh Gao043bad72015-09-22 11:43:08 -0700277
278def main():
Josh Gao466e2892017-07-13 15:39:05 -0700279 required_env = ["ANDROID_BUILD_TOP",
280 "ANDROID_PRODUCT_OUT", "TARGET_PRODUCT"]
281 for env in required_env:
282 if env not in os.environ:
283 sys.exit(
284 "Environment variable '{}' not defined, have you run lunch?".format(env))
285
Josh Gao043bad72015-09-22 11:43:08 -0700286 args = parse_args()
287 device = args.device
Josh Gao44b84a82015-10-28 11:57:37 -0700288
289 if device is None:
290 sys.exit("ERROR: Failed to find device.")
291
Josh Gao043bad72015-09-22 11:43:08 -0700292 root = os.environ["ANDROID_BUILD_TOP"]
Josh Gao466e2892017-07-13 15:39:05 -0700293 sysroot = os.path.join(os.environ["ANDROID_PRODUCT_OUT"], "symbols")
Josh Gao043bad72015-09-22 11:43:08 -0700294
295 # Make sure the environment matches the attached device.
Elliott Hughes1a2f12d2017-06-02 13:15:59 -0700296 verify_device(root, device)
Josh Gao043bad72015-09-22 11:43:08 -0700297
298 debug_socket = "/data/local/tmp/debug_socket"
299 pid = None
300 run_cmd = None
301
302 # Fetch binary for -p, -n.
David Pursell639d1c42015-10-20 15:38:32 -0700303 binary_file, pid, run_cmd = handle_switches(args, sysroot)
Josh Gao043bad72015-09-22 11:43:08 -0700304
305 with binary_file:
306 arch = gdbrunner.get_binary_arch(binary_file)
307 is64bit = arch.endswith("64")
308
309 # Make sure we have the linker
310 ensure_linker(device, sysroot, is64bit)
311
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700312 tracer_pid = get_tracer_pid(device, pid)
313 if tracer_pid == 0:
Peter Collingbourne63bf1082018-12-19 20:51:42 -0800314 cmd_prefix = args.su_cmd
315 if args.env:
316 cmd_prefix += ['env'] + [v[0] for v in args.env]
317
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700318 # Start gdbserver.
319 gdbserver_local_path = get_gdbserver_path(root, arch)
320 gdbserver_remote_path = "/data/local/tmp/{}-gdbserver".format(arch)
321 gdbrunner.start_gdbserver(
322 device, gdbserver_local_path, gdbserver_remote_path,
323 target_pid=pid, run_cmd=run_cmd, debug_socket=debug_socket,
Peter Collingbourne63bf1082018-12-19 20:51:42 -0800324 port=args.port, run_as_cmd=cmd_prefix)
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700325 else:
326 print "Connecting to tracing pid {} using local port {}".format(tracer_pid, args.port)
327 gdbrunner.forward_gdbserver_port(device, local=args.port,
328 remote="tcp:{}".format(args.port))
Josh Gao043bad72015-09-22 11:43:08 -0700329
Josh Gao043bad72015-09-22 11:43:08 -0700330 # Find where gdb is
331 if sys.platform.startswith("linux"):
332 platform_name = "linux-x86"
333 elif sys.platform.startswith("darwin"):
334 platform_name = "darwin-x86"
335 else:
336 sys.exit("Unknown platform: {}".format(sys.platform))
Alex Light92476652019-01-17 11:18:48 -0800337
Josh Gao043bad72015-09-22 11:43:08 -0700338 gdb_path = os.path.join(root, "prebuilts", "gdb", platform_name, "bin",
339 "gdb")
Alex Light92476652019-01-17 11:18:48 -0800340 # Generate a gdb script.
341 setup_commands = generate_setup_script(gdbpath=gdb_path,
342 sysroot=sysroot,
343 binary_file=binary_file,
344 is64bit=is64bit,
345 port=args.port,
346 debugger=args.setup_forwarding or "gdb")
Josh Gao043bad72015-09-22 11:43:08 -0700347
Alex Light92476652019-01-17 11:18:48 -0800348 if not args.setup_forwarding:
349 # Print a newline to separate our messages from the GDB session.
350 print("")
David Pursell639d1c42015-10-20 15:38:32 -0700351
Alex Light92476652019-01-17 11:18:48 -0800352 # Start gdb.
353 gdbrunner.start_gdb(gdb_path, setup_commands)
354 else:
355 print("")
356 print setup_commands
357 print("")
358 if args.setup_forwarding == "vscode":
359 print textwrap.dedent("""
360 Paste the above json into .vscode/launch.json and start the debugger as
361 normal. Press enter in this terminal once debugging is finished to shutdown
362 the gdbserver and close all the ports.""")
363 else:
364 print textwrap.dedent("""
365 Paste the above gdb commands into the gdb frontend to setup the gdbserver
366 connection. Press enter in this terminal once debugging is finished to
367 shutdown the gdbserver and close all the ports.""")
368 print("")
369 raw_input("Press enter to shutdown gdbserver")
Josh Gao043bad72015-09-22 11:43:08 -0700370
371if __name__ == "__main__":
372 main()