blob: e10a7205125204df143d7e930fc792507618540b [file] [log] [blame]
Elliott Hughes17de6ce2021-06-23 18:00:46 -07001#!/usr/bin/env python3
Josh Gao043bad72015-09-22 11:43:08 -07002#
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
Josh Gao043bad72015-09-22 11:43:08 -070018import argparse
Alex Light92476652019-01-17 11:18:48 -080019import json
Josh Gao043bad72015-09-22 11:43:08 -070020import logging
21import os
Nikita Putikhin7da179e2023-07-03 07:37:58 +000022import pathlib
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
Nikita Putikhin7da179e2023-07-03 07:37:58 +000030from typing import Any, BinaryIO
Josh Gao043bad72015-09-22 11:43:08 -070031
Nikita Putikhin7da179e2023-07-03 07:37:58 +000032import adb
Josh Gao043bad72015-09-22 11:43:08 -070033# Shared functions across gdbclient.py and ndk-gdb.py.
34import gdbrunner
35
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -070036g_temp_dirs = []
37
Nikita Putikhin7da179e2023-07-03 07:37:58 +000038g_vscode_config_marker_begin = '// #lldbclient-generated-begin'
39g_vscode_config_marker_end = '// #lldbclient-generated-end'
40
Haibo Huange194fce2020-01-06 14:40:27 -080041
Nikita Putikhin516960e2023-05-31 21:57:38 +000042def read_toolchain_config(root: str) -> str:
Pirama Arumuga Nainarf7f95442021-06-30 13:31:41 -070043 """Finds out current toolchain version."""
44 version_output = subprocess.check_output(
45 f'{root}/build/soong/scripts/get_clang_version.py',
46 text=True)
47 return version_output.strip()
Haibo Huange194fce2020-01-06 14:40:27 -080048
49
Nikita Putikhin516960e2023-05-31 21:57:38 +000050def get_lldb_path(toolchain_path: str) -> str | None:
Haibo Huange4d8bfd2020-07-22 16:37:35 -070051 for lldb_name in ['lldb.sh', 'lldb.cmd', 'lldb', 'lldb.exe']:
52 debugger_path = os.path.join(toolchain_path, "bin", lldb_name)
53 if os.path.isfile(debugger_path):
54 return debugger_path
55 return None
56
57
Nikita Putikhin516960e2023-05-31 21:57:38 +000058def get_lldb_server_path(root: str, clang_base: str, clang_version: str, arch: str) -> str:
Haibo Huange194fce2020-01-06 14:40:27 -080059 arch = {
60 'arm': 'arm',
61 'arm64': 'aarch64',
Samuel Hollandc434dec2023-06-16 09:35:40 -070062 'riscv64': 'riscv64',
Haibo Huange194fce2020-01-06 14:40:27 -080063 'x86': 'i386',
64 'x86_64': 'x86_64',
65 }[arch]
66 return os.path.join(root, clang_base, "linux-x86",
67 clang_version, "runtimes_ndk_cxx", arch, "lldb-server")
68
69
Nikita Putikhin516960e2023-05-31 21:57:38 +000070def get_tracer_pid(device: adb.AndroidDevice, pid: int | str | None) -> int:
Elliott Hughes89e1ecf2017-06-30 14:03:32 -070071 if pid is None:
72 return 0
73
74 line, _ = device.shell(["grep", "-e", "^TracerPid:", "/proc/{}/status".format(pid)])
75 tracer_pid = re.sub('TracerPid:\t(.*)\n', r'\1', line)
76 return int(tracer_pid)
77
78
Nikita Putikhin516960e2023-05-31 21:57:38 +000079def parse_args() -> argparse.Namespace:
Josh Gao043bad72015-09-22 11:43:08 -070080 parser = gdbrunner.ArgumentParser()
81
82 group = parser.add_argument_group(title="attach target")
83 group = group.add_mutually_exclusive_group(required=True)
84 group.add_argument(
85 "-p", dest="target_pid", metavar="PID", type=int,
86 help="attach to a process with specified PID")
87 group.add_argument(
88 "-n", dest="target_name", metavar="NAME",
89 help="attach to a process with specified name")
90 group.add_argument(
91 "-r", dest="run_cmd", metavar="CMD", nargs=argparse.REMAINDER,
92 help="run a binary on the device, with args")
93
94 parser.add_argument(
95 "--port", nargs="?", default="5039",
AdityaK50c9af72023-07-14 16:02:43 -070096 help="Unused **host** port to forward the debug_socket to.[default: 5039]")
Josh Gao043bad72015-09-22 11:43:08 -070097 parser.add_argument(
98 "--user", nargs="?", default="root",
99 help="user to run commands as on the device [default: root]")
Alex Light92476652019-01-17 11:18:48 -0800100 parser.add_argument(
Alex Lighta8f224d2020-11-10 10:30:19 -0800101 "--setup-forwarding", default=None,
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700102 choices=["lldb", "vscode-lldb"],
103 help=("Set up lldb-server and port forwarding. Prints commands or " +
Alex Light92476652019-01-17 11:18:48 -0800104 ".vscode/launch.json configuration needed to connect the debugging " +
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000105 "client to the server. 'vscode' with lldb and 'vscode-lldb' both " +
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700106 "require the 'vadimcn.vscode-lldb' extension."))
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000107 parser.add_argument(
108 "--vscode-launch-props", default=None,
109 dest="vscode_launch_props",
110 help=("JSON with extra properties to add to launch parameters when using " +
111 "vscode-lldb forwarding."))
Nikita Putikhin7da179e2023-07-03 07:37:58 +0000112 parser.add_argument(
113 "--vscode-launch-file", default=None,
114 dest="vscode_launch_file",
115 help=textwrap.dedent(f"""Path to .vscode/launch.json file for the generated launch
116 config when using vscode-lldb forwarding. The file needs to
117 contain two marker lines: '{g_vscode_config_marker_begin}'
118 and '{g_vscode_config_marker_end}'. The config will be written inline
119 between these lines, replacing any text that is already there."""))
Haibo Huange194fce2020-01-06 14:40:27 -0800120
Peter Collingbourne63bf1082018-12-19 20:51:42 -0800121 parser.add_argument(
122 "--env", nargs=1, action="append", metavar="VAR=VALUE",
123 help="set environment variable when running a binary")
Peter Collingbourneba548262022-03-03 12:17:43 -0800124 parser.add_argument(
125 "--chroot", nargs='?', default="", metavar="PATH",
126 help="run command in a chroot in the given directory")
Peter Collingbourne63bf1082018-12-19 20:51:42 -0800127
Josh Gao043bad72015-09-22 11:43:08 -0700128 return parser.parse_args()
129
130
Nikita Putikhin516960e2023-05-31 21:57:38 +0000131def verify_device(device: adb.AndroidDevice) -> None:
Junichi Uekawa6612c922020-09-07 11:20:59 +0900132 names = set([device.get_prop("ro.build.product"), device.get_prop("ro.product.name")])
Josh Gao466e2892017-07-13 15:39:05 -0700133 target_device = os.environ["TARGET_PRODUCT"]
Junichi Uekawa6612c922020-09-07 11:20:59 +0900134 if target_device not in names:
AdityaK50c9af72023-07-14 16:02:43 -0700135 msg = "You used the wrong lunch: TARGET_PRODUCT ({}) does not match attached device ({})"
Nikita Putikhin516960e2023-05-31 21:57:38 +0000136 sys.exit(msg.format(target_device, ", ".join(n if n else "None" for n in names)))
Josh Gao043bad72015-09-22 11:43:08 -0700137
138
Nikita Putikhin516960e2023-05-31 21:57:38 +0000139def get_remote_pid(device: adb.AndroidDevice, process_name: str) -> int:
Josh Gao043bad72015-09-22 11:43:08 -0700140 processes = gdbrunner.get_processes(device)
141 if process_name not in processes:
142 msg = "failed to find running process {}".format(process_name)
143 sys.exit(msg)
144 pids = processes[process_name]
145 if len(pids) > 1:
146 msg = "multiple processes match '{}': {}".format(process_name, pids)
147 sys.exit(msg)
148
149 # Fetch the binary using the PID later.
150 return pids[0]
151
152
Nikita Putikhin516960e2023-05-31 21:57:38 +0000153def make_temp_dir(prefix: str) -> str:
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700154 global g_temp_dirs
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700155 result = tempfile.mkdtemp(prefix='lldbclient-linker-')
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700156 g_temp_dirs.append(result)
157 return result
158
159
Nikita Putikhin516960e2023-05-31 21:57:38 +0000160def ensure_linker(device: adb.AndroidDevice, sysroot: str, interp: str | None) -> str | None:
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700161 """Ensure that the device's linker exists on the host.
162
163 PT_INTERP is usually /system/bin/linker[64], but on the device, that file is
164 a symlink to /apex/com.android.runtime/bin/linker[64]. The symbolized linker
165 binary on the host is located in ${sysroot}/apex, not in ${sysroot}/system,
166 so add the ${sysroot}/apex path to the solib search path.
167
168 PT_INTERP will be /system/bin/bootstrap/linker[64] for executables using the
169 non-APEX/bootstrap linker. No search path modification is needed.
170
171 For a tapas build, only an unbundled app is built, and there is no linker in
172 ${sysroot} at all, so copy the linker from the device.
173
174 Returns:
175 A directory to add to the soinfo search path or None if no directory
176 needs to be added.
177 """
178
179 # Static executables have no interpreter.
180 if interp is None:
181 return None
182
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700183 # lldb will search for the linker using the PT_INTERP path. First try to find
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700184 # it in the sysroot.
185 local_path = os.path.join(sysroot, interp.lstrip("/"))
186 if os.path.exists(local_path):
187 return None
188
189 # If the linker on the device is a symlink, search for the symlink's target
190 # in the sysroot directory.
191 interp_real, _ = device.shell(["realpath", interp])
192 interp_real = interp_real.strip()
193 local_path = os.path.join(sysroot, interp_real.lstrip("/"))
194 if os.path.exists(local_path):
195 if posixpath.basename(interp) == posixpath.basename(interp_real):
196 # Add the interpreter's directory to the search path.
197 return os.path.dirname(local_path)
198 else:
199 # If PT_INTERP is linker_asan[64], but the sysroot file is
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700200 # linker[64], then copy the local file to the name lldb expects.
201 result = make_temp_dir('lldbclient-linker-')
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700202 shutil.copy(local_path, os.path.join(result, posixpath.basename(interp)))
203 return result
204
205 # Pull the system linker.
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700206 result = make_temp_dir('lldbclient-linker-')
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700207 device.pull(interp, os.path.join(result, posixpath.basename(interp)))
208 return result
Josh Gao043bad72015-09-22 11:43:08 -0700209
210
Nikita Putikhin516960e2023-05-31 21:57:38 +0000211def handle_switches(args, sysroot: str) -> tuple[BinaryIO, int | None, str | None]:
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700212 """Fetch the targeted binary and determine how to attach lldb.
Josh Gao043bad72015-09-22 11:43:08 -0700213
214 Args:
215 args: Parsed arguments.
216 sysroot: Local sysroot path.
217
218 Returns:
219 (binary_file, attach_pid, run_cmd).
220 Precisely one of attach_pid or run_cmd will be None.
221 """
222
223 device = args.device
224 binary_file = None
225 pid = None
226 run_cmd = None
227
Josh Gao057c2732017-05-24 15:55:50 -0700228 args.su_cmd = ["su", args.user] if args.user else []
229
Josh Gao043bad72015-09-22 11:43:08 -0700230 if args.target_pid:
231 # Fetch the binary using the PID later.
232 pid = args.target_pid
233 elif args.target_name:
234 # Fetch the binary using the PID later.
235 pid = get_remote_pid(device, args.target_name)
236 elif args.run_cmd:
237 if not args.run_cmd[0]:
238 sys.exit("empty command passed to -r")
Josh Gao043bad72015-09-22 11:43:08 -0700239 run_cmd = args.run_cmd
Kevin Rocard258c89e2017-07-12 18:21:29 -0700240 if not run_cmd[0].startswith("/"):
241 try:
242 run_cmd[0] = gdbrunner.find_executable_path(device, args.run_cmd[0],
243 run_as_cmd=args.su_cmd)
244 except RuntimeError:
245 sys.exit("Could not find executable '{}' passed to -r, "
246 "please provide an absolute path.".format(args.run_cmd[0]))
247
David Pursell639d1c42015-10-20 15:38:32 -0700248 binary_file, local = gdbrunner.find_file(device, run_cmd[0], sysroot,
Josh Gao057c2732017-05-24 15:55:50 -0700249 run_as_cmd=args.su_cmd)
Josh Gao043bad72015-09-22 11:43:08 -0700250 if binary_file is None:
251 assert pid is not None
252 try:
David Pursell639d1c42015-10-20 15:38:32 -0700253 binary_file, local = gdbrunner.find_binary(device, pid, sysroot,
Josh Gao057c2732017-05-24 15:55:50 -0700254 run_as_cmd=args.su_cmd)
Josh Gao043bad72015-09-22 11:43:08 -0700255 except adb.ShellError:
256 sys.exit("failed to pull binary for PID {}".format(pid))
257
David Pursell639d1c42015-10-20 15:38:32 -0700258 if not local:
259 logging.warning("Couldn't find local unstripped executable in {},"
260 " symbols may not be available.".format(sysroot))
261
Josh Gao043bad72015-09-22 11:43:08 -0700262 return (binary_file, pid, run_cmd)
263
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000264def merge_launch_dict(base: dict[str, Any], to_add: dict[str, Any] | None) -> None:
265 """Merges two dicts describing VSCode launch.json properties: base and
266 to_add. Base is modified in-place with items from to_add.
267 Items from to_add that are not present in base are inserted. Items that are
268 present are merged following these rules:
269 - Lists are merged with to_add elements appended to the end of base
270 list. Only a list can be merged with a list.
271 - dicts are merged recursively. Only a dict can be merged with a dict.
272 - Other present values in base get overwritten with values from to_add.
273
274 The reason for these rules is that merging in new values should prefer to
275 expand the existing set instead of overwriting where possible.
276 """
277 if to_add is None:
278 return
279
280 for key, val in to_add.items():
281 if key not in base:
282 base[key] = val
283 else:
284 if isinstance(base[key], list) and not isinstance(val, list):
285 raise ValueError(f'Cannot merge non-list into list at key={key}. ' +
286 'You probably need to wrap your value into a list.')
287 if not isinstance(base[key], list) and isinstance(val, list):
288 raise ValueError(f'Cannot merge list into non-list at key={key}.')
289 if isinstance(base[key], dict) != isinstance(val, dict):
290 raise ValueError(f'Cannot merge dict and non-dict at key={key}')
291
292 # We don't allow the user to overwrite or interleave lists and don't allow
293 # to delete dict entries.
294 # It can be done but would make the implementation a bit more complicated
295 # and provides less value than adding elements.
296 # We expect that the config generated by gdbclient doesn't contain anything
297 # the user would want to remove.
298 if isinstance(base[key], list):
299 base[key] += val
300 elif isinstance(base[key], dict):
301 merge_launch_dict(base[key], val)
302 else:
303 base[key] = val
304
305
306def generate_vscode_lldb_script(root: str, sysroot: str, binary_name: str, port: str | int, solib_search_path: list[str], extra_props: dict[str, Any] | None) -> str:
Alex Lighta8f224d2020-11-10 10:30:19 -0800307 # TODO It would be nice if we didn't need to copy this or run the
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700308 # lldbclient.py program manually. Doing this would probably require
Alex Lighta8f224d2020-11-10 10:30:19 -0800309 # writing a vscode extension or modifying an existing one.
310 # TODO: https://code.visualstudio.com/api/references/vscode-api#debug and
311 # https://code.visualstudio.com/api/extension-guides/debugger-extension and
312 # https://github.com/vadimcn/vscode-lldb/blob/6b775c439992b6615e92f4938ee4e211f1b060cf/extension/pickProcess.ts#L6
313 res = {
314 "name": "(lldbclient.py) Attach {} (port: {})".format(binary_name.split("/")[-1], port),
315 "type": "lldb",
316 "request": "custom",
317 "relativePathBase": root,
318 "sourceMap": { "/b/f/w" : root, '': root, '.': root },
319 "initCommands": ['settings append target.exec-search-paths {}'.format(' '.join(solib_search_path))],
320 "targetCreateCommands": ["target create {}".format(binary_name),
321 "target modules search-paths add / {}/".format(sysroot)],
Peter Collingbourneb6b58a42023-03-15 14:11:00 -0700322 "processCreateCommands": ["gdb-remote {}".format(str(port))]
Alex Lighta8f224d2020-11-10 10:30:19 -0800323 }
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000324 merge_launch_dict(res, extra_props)
Alex Lighta8f224d2020-11-10 10:30:19 -0800325 return json.dumps(res, indent=4)
326
Nikita Putikhin516960e2023-05-31 21:57:38 +0000327def generate_lldb_script(root: str, sysroot: str, binary_name: str, port: str | int, solib_search_path: list[str]) -> str:
Haibo Huange194fce2020-01-06 14:40:27 -0800328 commands = []
329 commands.append(
330 'settings append target.exec-search-paths {}'.format(' '.join(solib_search_path)))
331
332 commands.append('target create {}'.format(binary_name))
Haibo Huang987436c2020-09-22 21:01:31 -0700333 # For RBE support.
334 commands.append("settings append target.source-map '/b/f/w' '{}'".format(root))
335 commands.append("settings append target.source-map '' '{}'".format(root))
Haibo Huange194fce2020-01-06 14:40:27 -0800336 commands.append('target modules search-paths add / {}/'.format(sysroot))
Peter Collingbourneb6b58a42023-03-15 14:11:00 -0700337 commands.append('gdb-remote {}'.format(str(port)))
Haibo Huange194fce2020-01-06 14:40:27 -0800338 return '\n'.join(commands)
339
340
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000341def generate_setup_script(sysroot: str, linker_search_dir: str | None, binary_name: str, is64bit: bool, port: str | int, debugger: str, vscode_launch_props: dict[str, Any] | None) -> str:
Alex Light92476652019-01-17 11:18:48 -0800342 # Generate a setup script.
Alex Light92476652019-01-17 11:18:48 -0800343 root = os.environ["ANDROID_BUILD_TOP"]
344 symbols_dir = os.path.join(sysroot, "system", "lib64" if is64bit else "lib")
345 vendor_dir = os.path.join(sysroot, "vendor", "lib64" if is64bit else "lib")
346
347 solib_search_path = []
348 symbols_paths = ["", "hw", "ssl/engines", "drm", "egl", "soundfx"]
349 vendor_paths = ["", "hw", "egl"]
350 solib_search_path += [os.path.join(symbols_dir, x) for x in symbols_paths]
351 solib_search_path += [os.path.join(vendor_dir, x) for x in vendor_paths]
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700352 if linker_search_dir is not None:
353 solib_search_path += [linker_search_dir]
Alex Light92476652019-01-17 11:18:48 -0800354
Alex Lighta8f224d2020-11-10 10:30:19 -0800355 if debugger == "vscode-lldb":
356 return generate_vscode_lldb_script(
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000357 root, sysroot, binary_name, port, solib_search_path, vscode_launch_props)
Haibo Huange194fce2020-01-06 14:40:27 -0800358 elif debugger == 'lldb':
359 return generate_lldb_script(
Nikita Putikhin516960e2023-05-31 21:57:38 +0000360 root, sysroot, binary_name, port, solib_search_path)
Alex Light92476652019-01-17 11:18:48 -0800361 else:
362 raise Exception("Unknown debugger type " + debugger)
363
Josh Gao043bad72015-09-22 11:43:08 -0700364
Nikita Putikhin7da179e2023-07-03 07:37:58 +0000365def insert_commands_into_vscode_config(dst_launch_config: str, setup_commands: str) -> str:
366 """Inserts setup commands into launch config between two marker lines.
367 Marker lines are set in global variables g_vscode_config_marker_end and g_vscode_config_marker_end.
368 The commands are inserted with the same indentation as the first marker line.
369
370 Args:
371 dst_launch_config: Config to insert commands into.
372 setup_commands: Commands to insert.
373 Returns:
374 Config with inserted commands.
375 Raises:
376 ValueError if the begin marker is not found or not terminated with an end marker.
377 """
378
379 # We expect the files to be small (~10s KB), so we use simple string concatenation
380 # for simplicity and readability even if it is slower.
381 output = ""
382 found_at_least_one_begin = False
383 unterminated_begin_line = None
384
385 # It might be tempting to rewrite this using find() or even regexes,
386 # but keeping track of line numbers, preserving whitespace, and detecting indent
387 # becomes tricky enough that this simple loop is more clear.
388 for linenum, line in enumerate(dst_launch_config.splitlines(keepends=True), start=1):
389 if unterminated_begin_line != None:
390 if line.strip() == g_vscode_config_marker_end:
391 unterminated_begin_line = None
392 else:
393 continue
394 output += line
395 if line.strip() == g_vscode_config_marker_begin:
396 found_at_least_one_begin = True
397 unterminated_begin_line = linenum
398 marker_indent = line[:line.find(g_vscode_config_marker_begin)]
399 output += textwrap.indent(setup_commands, marker_indent) + '\n'
400
401 if not found_at_least_one_begin:
402 raise ValueError(f"Did not find begin marker line '{g_vscode_config_marker_begin}' " +
403 "in the VSCode launch file")
404
405 if unterminated_begin_line is not None:
406 raise ValueError(f"Unterminated begin marker at line {unterminated_begin_line} " +
407 f"in the VSCode launch file. Add end marker line to file: '{g_vscode_config_marker_end}'")
408
409 return output
410
411
412def replace_file_contents(dst_path: os.PathLike, contents: str) -> None:
413 """Replaces the contents of the file pointed to by dst_path.
414
415 This function writes the new contents into a temporary file, then atomically swaps it with
416 the target file. This way if a write fails, the original file is not overwritten.
417
418 Args:
419 dst_path: The path to the file to be replaced.
420 contents: The new contents of the file.
421 Raises:
422 Forwards exceptions from underlying filesystem methods.
423 """
424 tempf = tempfile.NamedTemporaryFile('w', delete=False)
425 try:
426 tempf.write(contents)
427 os.replace(tempf.name, dst_path)
428 except:
429 os.remove(tempf.name)
430 raise
431
432
433def write_vscode_config(vscode_launch_file: pathlib.Path, setup_commands: str) -> None:
434 """Writes setup_commands into the file pointed by vscode_launch_file.
435
436 See insert_commands_into_vscode_config for the description of how the setup commands are written.
437 """
438 contents = insert_commands_into_vscode_config(vscode_launch_file.read_text(), setup_commands)
439 replace_file_contents(vscode_launch_file, contents)
440
441
Nikita Putikhin516960e2023-05-31 21:57:38 +0000442def do_main() -> None:
Josh Gao466e2892017-07-13 15:39:05 -0700443 required_env = ["ANDROID_BUILD_TOP",
444 "ANDROID_PRODUCT_OUT", "TARGET_PRODUCT"]
445 for env in required_env:
446 if env not in os.environ:
447 sys.exit(
448 "Environment variable '{}' not defined, have you run lunch?".format(env))
449
Josh Gao043bad72015-09-22 11:43:08 -0700450 args = parse_args()
451 device = args.device
Josh Gao44b84a82015-10-28 11:57:37 -0700452
453 if device is None:
454 sys.exit("ERROR: Failed to find device.")
455
Josh Gao043bad72015-09-22 11:43:08 -0700456 root = os.environ["ANDROID_BUILD_TOP"]
Josh Gao466e2892017-07-13 15:39:05 -0700457 sysroot = os.path.join(os.environ["ANDROID_PRODUCT_OUT"], "symbols")
Josh Gao043bad72015-09-22 11:43:08 -0700458
459 # Make sure the environment matches the attached device.
Peter Collingbourneba548262022-03-03 12:17:43 -0800460 # Skip when running in a chroot because the chroot lunch target may not
461 # match the device's lunch target.
462 if not args.chroot:
Nikita Putikhin516960e2023-05-31 21:57:38 +0000463 verify_device(device)
Josh Gao043bad72015-09-22 11:43:08 -0700464
465 debug_socket = "/data/local/tmp/debug_socket"
466 pid = None
467 run_cmd = None
468
469 # Fetch binary for -p, -n.
David Pursell639d1c42015-10-20 15:38:32 -0700470 binary_file, pid, run_cmd = handle_switches(args, sysroot)
Josh Gao043bad72015-09-22 11:43:08 -0700471
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000472 vscode_launch_props = None
473 if args.vscode_launch_props:
474 if args.setup_forwarding != "vscode-lldb":
Nikita Putikhin7da179e2023-07-03 07:37:58 +0000475 raise ValueError(
476 'vscode-launch-props requires --setup-forwarding=vscode-lldb')
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000477 vscode_launch_props = json.loads(args.vscode_launch_props)
478
Nikita Putikhin7da179e2023-07-03 07:37:58 +0000479 vscode_launch_file = None
480 if args.vscode_launch_file:
481 if args.setup_forwarding != "vscode-lldb":
482 raise ValueError(
483 'vscode-launch-file requires --setup-forwarding=vscode-lldb')
484 vscode_launch_file = args.vscode_launch_file
485
Josh Gao043bad72015-09-22 11:43:08 -0700486 with binary_file:
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700487 if sys.platform.startswith("linux"):
488 platform_name = "linux-x86"
489 elif sys.platform.startswith("darwin"):
490 platform_name = "darwin-x86"
491 else:
492 sys.exit("Unknown platform: {}".format(sys.platform))
493
Josh Gao043bad72015-09-22 11:43:08 -0700494 arch = gdbrunner.get_binary_arch(binary_file)
495 is64bit = arch.endswith("64")
496
497 # Make sure we have the linker
Pirama Arumuga Nainarf7f95442021-06-30 13:31:41 -0700498 clang_base = 'prebuilts/clang/host'
499 clang_version = read_toolchain_config(root)
Haibo Huange194fce2020-01-06 14:40:27 -0800500 toolchain_path = os.path.join(root, clang_base, platform_name,
501 clang_version)
502 llvm_readobj_path = os.path.join(toolchain_path, "bin", "llvm-readobj")
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700503 interp = gdbrunner.get_binary_interp(binary_file.name, llvm_readobj_path)
504 linker_search_dir = ensure_linker(device, sysroot, interp)
Josh Gao043bad72015-09-22 11:43:08 -0700505
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700506 tracer_pid = get_tracer_pid(device, pid)
507 if tracer_pid == 0:
Peter Collingbourne63bf1082018-12-19 20:51:42 -0800508 cmd_prefix = args.su_cmd
509 if args.env:
510 cmd_prefix += ['env'] + [v[0] for v in args.env]
511
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700512 # Start lldb-server.
513 server_local_path = get_lldb_server_path(root, clang_base, clang_version, arch)
514 server_remote_path = "/data/local/tmp/{}-lldb-server".format(arch)
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700515 gdbrunner.start_gdbserver(
Haibo Huange194fce2020-01-06 14:40:27 -0800516 device, server_local_path, server_remote_path,
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700517 target_pid=pid, run_cmd=run_cmd, debug_socket=debug_socket,
Peter Collingbourneba548262022-03-03 12:17:43 -0800518 port=args.port, run_as_cmd=cmd_prefix, lldb=True, chroot=args.chroot)
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700519 else:
Haibo Huange194fce2020-01-06 14:40:27 -0800520 print(
521 "Connecting to tracing pid {} using local port {}".format(
522 tracer_pid, args.port))
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700523 gdbrunner.forward_gdbserver_port(device, local=args.port,
524 remote="tcp:{}".format(args.port))
Josh Gao043bad72015-09-22 11:43:08 -0700525
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700526 debugger_path = get_lldb_path(toolchain_path)
527 debugger = args.setup_forwarding or 'lldb'
Haibo Huange194fce2020-01-06 14:40:27 -0800528
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700529 # Generate the lldb script.
Nikita Putikhin516960e2023-05-31 21:57:38 +0000530 setup_commands = generate_setup_script(sysroot=sysroot,
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700531 linker_search_dir=linker_search_dir,
Nikita Putikhin516960e2023-05-31 21:57:38 +0000532 binary_name=binary_file.name,
Alex Light92476652019-01-17 11:18:48 -0800533 is64bit=is64bit,
534 port=args.port,
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000535 debugger=debugger,
536 vscode_launch_props=vscode_launch_props)
Josh Gao043bad72015-09-22 11:43:08 -0700537
Alex Lighta8f224d2020-11-10 10:30:19 -0800538 if not args.setup_forwarding:
Alex Light92476652019-01-17 11:18:48 -0800539 # Print a newline to separate our messages from the GDB session.
540 print("")
David Pursell639d1c42015-10-20 15:38:32 -0700541
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700542 # Start lldb.
543 gdbrunner.start_gdb(debugger_path, setup_commands, lldb=True)
Alex Light92476652019-01-17 11:18:48 -0800544 else:
Nikita Putikhin7da179e2023-07-03 07:37:58 +0000545 if args.setup_forwarding == "vscode-lldb" and vscode_launch_file:
546 write_vscode_config(pathlib.Path(vscode_launch_file) , setup_commands)
547 print(f"Generated config written to '{vscode_launch_file}'")
Alex Light92476652019-01-17 11:18:48 -0800548 else:
Nikita Putikhin7da179e2023-07-03 07:37:58 +0000549 print("")
550 print(setup_commands)
551 print("")
552 if args.setup_forwarding == "vscode-lldb":
553 print(textwrap.dedent("""
554 Paste the above json into .vscode/launch.json and start the debugger as
555 normal."""))
556 else:
557 print(textwrap.dedent("""
558 Paste the lldb commands above into the lldb frontend to set up the
559 lldb-server connection."""))
560
561 print(textwrap.dedent("""
562 Press enter in this terminal once debugging is finished to shut lldb-server
563 down and close all the ports."""))
Alex Light92476652019-01-17 11:18:48 -0800564 print("")
Siarhei Vishniakou9c4f1b32021-12-02 10:43:14 -0800565 input("Press enter to shut down lldb-server")
Josh Gao043bad72015-09-22 11:43:08 -0700566
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700567
568def main():
569 try:
570 do_main()
571 finally:
572 global g_temp_dirs
573 for temp_dir in g_temp_dirs:
574 shutil.rmtree(temp_dir)
575
576
Josh Gao043bad72015-09-22 11:43:08 -0700577if __name__ == "__main__":
578 main()