blob: 81d9992f5538936ad58b656e0a8f8bb3ff11daec [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",
Nikita Putikhincfff4e32024-04-22 13:04:45 +0200126 help="run command in a chroot in the given directory. Cannot be used with --cwd.")
127 parser.add_argument(
128 "--cwd", nargs='?', default="", metavar="PATH",
129 help="working directory for the command. Cannot be used with --chroot.")
Peter Collingbourne63bf1082018-12-19 20:51:42 -0800130
Josh Gao043bad72015-09-22 11:43:08 -0700131 return parser.parse_args()
132
133
Nikita Putikhin516960e2023-05-31 21:57:38 +0000134def verify_device(device: adb.AndroidDevice) -> None:
Junichi Uekawa6612c922020-09-07 11:20:59 +0900135 names = set([device.get_prop("ro.build.product"), device.get_prop("ro.product.name")])
Josh Gao466e2892017-07-13 15:39:05 -0700136 target_device = os.environ["TARGET_PRODUCT"]
Junichi Uekawa6612c922020-09-07 11:20:59 +0900137 if target_device not in names:
AdityaK50c9af72023-07-14 16:02:43 -0700138 msg = "You used the wrong lunch: TARGET_PRODUCT ({}) does not match attached device ({})"
Nikita Putikhin516960e2023-05-31 21:57:38 +0000139 sys.exit(msg.format(target_device, ", ".join(n if n else "None" for n in names)))
Josh Gao043bad72015-09-22 11:43:08 -0700140
141
Nikita Putikhincfff4e32024-04-22 13:04:45 +0200142def get_device_dir_exists(device: adb.AndroidDevice, dir: str) -> bool:
143 exit_code, _, _ = device.shell_nocheck(['[', '-d', dir, ']'])
144 return exit_code == 0
145
146
Nikita Putikhin516960e2023-05-31 21:57:38 +0000147def get_remote_pid(device: adb.AndroidDevice, process_name: str) -> int:
Josh Gao043bad72015-09-22 11:43:08 -0700148 processes = gdbrunner.get_processes(device)
149 if process_name not in processes:
150 msg = "failed to find running process {}".format(process_name)
151 sys.exit(msg)
152 pids = processes[process_name]
153 if len(pids) > 1:
154 msg = "multiple processes match '{}': {}".format(process_name, pids)
155 sys.exit(msg)
156
157 # Fetch the binary using the PID later.
158 return pids[0]
159
160
Nikita Putikhin516960e2023-05-31 21:57:38 +0000161def make_temp_dir(prefix: str) -> str:
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700162 global g_temp_dirs
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700163 result = tempfile.mkdtemp(prefix='lldbclient-linker-')
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700164 g_temp_dirs.append(result)
165 return result
166
167
Nikita Putikhin516960e2023-05-31 21:57:38 +0000168def ensure_linker(device: adb.AndroidDevice, sysroot: str, interp: str | None) -> str | None:
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700169 """Ensure that the device's linker exists on the host.
170
171 PT_INTERP is usually /system/bin/linker[64], but on the device, that file is
172 a symlink to /apex/com.android.runtime/bin/linker[64]. The symbolized linker
173 binary on the host is located in ${sysroot}/apex, not in ${sysroot}/system,
174 so add the ${sysroot}/apex path to the solib search path.
175
176 PT_INTERP will be /system/bin/bootstrap/linker[64] for executables using the
177 non-APEX/bootstrap linker. No search path modification is needed.
178
179 For a tapas build, only an unbundled app is built, and there is no linker in
180 ${sysroot} at all, so copy the linker from the device.
181
182 Returns:
183 A directory to add to the soinfo search path or None if no directory
184 needs to be added.
185 """
186
187 # Static executables have no interpreter.
188 if interp is None:
189 return None
190
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700191 # lldb will search for the linker using the PT_INTERP path. First try to find
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700192 # it in the sysroot.
193 local_path = os.path.join(sysroot, interp.lstrip("/"))
194 if os.path.exists(local_path):
195 return None
196
197 # If the linker on the device is a symlink, search for the symlink's target
198 # in the sysroot directory.
199 interp_real, _ = device.shell(["realpath", interp])
200 interp_real = interp_real.strip()
201 local_path = os.path.join(sysroot, interp_real.lstrip("/"))
202 if os.path.exists(local_path):
203 if posixpath.basename(interp) == posixpath.basename(interp_real):
204 # Add the interpreter's directory to the search path.
205 return os.path.dirname(local_path)
206 else:
207 # If PT_INTERP is linker_asan[64], but the sysroot file is
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700208 # linker[64], then copy the local file to the name lldb expects.
209 result = make_temp_dir('lldbclient-linker-')
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700210 shutil.copy(local_path, os.path.join(result, posixpath.basename(interp)))
211 return result
212
213 # Pull the system linker.
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700214 result = make_temp_dir('lldbclient-linker-')
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700215 device.pull(interp, os.path.join(result, posixpath.basename(interp)))
216 return result
Josh Gao043bad72015-09-22 11:43:08 -0700217
218
Nikita Putikhin516960e2023-05-31 21:57:38 +0000219def handle_switches(args, sysroot: str) -> tuple[BinaryIO, int | None, str | None]:
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700220 """Fetch the targeted binary and determine how to attach lldb.
Josh Gao043bad72015-09-22 11:43:08 -0700221
222 Args:
223 args: Parsed arguments.
224 sysroot: Local sysroot path.
225
226 Returns:
227 (binary_file, attach_pid, run_cmd).
228 Precisely one of attach_pid or run_cmd will be None.
229 """
230
231 device = args.device
232 binary_file = None
233 pid = None
234 run_cmd = None
235
Josh Gao057c2732017-05-24 15:55:50 -0700236 args.su_cmd = ["su", args.user] if args.user else []
237
Josh Gao043bad72015-09-22 11:43:08 -0700238 if args.target_pid:
239 # Fetch the binary using the PID later.
240 pid = args.target_pid
241 elif args.target_name:
242 # Fetch the binary using the PID later.
243 pid = get_remote_pid(device, args.target_name)
244 elif args.run_cmd:
245 if not args.run_cmd[0]:
246 sys.exit("empty command passed to -r")
Josh Gao043bad72015-09-22 11:43:08 -0700247 run_cmd = args.run_cmd
Kevin Rocard258c89e2017-07-12 18:21:29 -0700248 if not run_cmd[0].startswith("/"):
249 try:
250 run_cmd[0] = gdbrunner.find_executable_path(device, args.run_cmd[0],
251 run_as_cmd=args.su_cmd)
252 except RuntimeError:
253 sys.exit("Could not find executable '{}' passed to -r, "
254 "please provide an absolute path.".format(args.run_cmd[0]))
255
David Pursell639d1c42015-10-20 15:38:32 -0700256 binary_file, local = gdbrunner.find_file(device, run_cmd[0], sysroot,
Josh Gao057c2732017-05-24 15:55:50 -0700257 run_as_cmd=args.su_cmd)
Josh Gao043bad72015-09-22 11:43:08 -0700258 if binary_file is None:
259 assert pid is not None
260 try:
David Pursell639d1c42015-10-20 15:38:32 -0700261 binary_file, local = gdbrunner.find_binary(device, pid, sysroot,
Josh Gao057c2732017-05-24 15:55:50 -0700262 run_as_cmd=args.su_cmd)
Josh Gao043bad72015-09-22 11:43:08 -0700263 except adb.ShellError:
264 sys.exit("failed to pull binary for PID {}".format(pid))
265
David Pursell639d1c42015-10-20 15:38:32 -0700266 if not local:
267 logging.warning("Couldn't find local unstripped executable in {},"
268 " symbols may not be available.".format(sysroot))
269
Josh Gao043bad72015-09-22 11:43:08 -0700270 return (binary_file, pid, run_cmd)
271
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000272def merge_launch_dict(base: dict[str, Any], to_add: dict[str, Any] | None) -> None:
273 """Merges two dicts describing VSCode launch.json properties: base and
274 to_add. Base is modified in-place with items from to_add.
275 Items from to_add that are not present in base are inserted. Items that are
276 present are merged following these rules:
277 - Lists are merged with to_add elements appended to the end of base
278 list. Only a list can be merged with a list.
279 - dicts are merged recursively. Only a dict can be merged with a dict.
280 - Other present values in base get overwritten with values from to_add.
281
282 The reason for these rules is that merging in new values should prefer to
283 expand the existing set instead of overwriting where possible.
284 """
285 if to_add is None:
286 return
287
288 for key, val in to_add.items():
289 if key not in base:
290 base[key] = val
291 else:
292 if isinstance(base[key], list) and not isinstance(val, list):
293 raise ValueError(f'Cannot merge non-list into list at key={key}. ' +
294 'You probably need to wrap your value into a list.')
295 if not isinstance(base[key], list) and isinstance(val, list):
296 raise ValueError(f'Cannot merge list into non-list at key={key}.')
297 if isinstance(base[key], dict) != isinstance(val, dict):
298 raise ValueError(f'Cannot merge dict and non-dict at key={key}')
299
300 # We don't allow the user to overwrite or interleave lists and don't allow
301 # to delete dict entries.
302 # It can be done but would make the implementation a bit more complicated
303 # and provides less value than adding elements.
304 # We expect that the config generated by gdbclient doesn't contain anything
305 # the user would want to remove.
306 if isinstance(base[key], list):
307 base[key] += val
308 elif isinstance(base[key], dict):
309 merge_launch_dict(base[key], val)
310 else:
311 base[key] = val
312
313
314def 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 -0800315 # TODO It would be nice if we didn't need to copy this or run the
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700316 # lldbclient.py program manually. Doing this would probably require
Alex Lighta8f224d2020-11-10 10:30:19 -0800317 # writing a vscode extension or modifying an existing one.
318 # TODO: https://code.visualstudio.com/api/references/vscode-api#debug and
319 # https://code.visualstudio.com/api/extension-guides/debugger-extension and
320 # https://github.com/vadimcn/vscode-lldb/blob/6b775c439992b6615e92f4938ee4e211f1b060cf/extension/pickProcess.ts#L6
321 res = {
322 "name": "(lldbclient.py) Attach {} (port: {})".format(binary_name.split("/")[-1], port),
323 "type": "lldb",
324 "request": "custom",
325 "relativePathBase": root,
326 "sourceMap": { "/b/f/w" : root, '': root, '.': root },
327 "initCommands": ['settings append target.exec-search-paths {}'.format(' '.join(solib_search_path))],
328 "targetCreateCommands": ["target create {}".format(binary_name),
329 "target modules search-paths add / {}/".format(sysroot)],
Peter Collingbourneb6b58a42023-03-15 14:11:00 -0700330 "processCreateCommands": ["gdb-remote {}".format(str(port))]
Alex Lighta8f224d2020-11-10 10:30:19 -0800331 }
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000332 merge_launch_dict(res, extra_props)
Alex Lighta8f224d2020-11-10 10:30:19 -0800333 return json.dumps(res, indent=4)
334
Nikita Putikhin516960e2023-05-31 21:57:38 +0000335def 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 -0800336 commands = []
337 commands.append(
338 'settings append target.exec-search-paths {}'.format(' '.join(solib_search_path)))
339
340 commands.append('target create {}'.format(binary_name))
Haibo Huang987436c2020-09-22 21:01:31 -0700341 # For RBE support.
342 commands.append("settings append target.source-map '/b/f/w' '{}'".format(root))
343 commands.append("settings append target.source-map '' '{}'".format(root))
Haibo Huange194fce2020-01-06 14:40:27 -0800344 commands.append('target modules search-paths add / {}/'.format(sysroot))
Prashanth Swaminathanf8efa362024-03-13 10:07:46 -0700345 commands.append('# If the below `gdb-remote` fails, run the command manually, '
346 + 'as it may have raced with lldbserver startup.')
Peter Collingbourneb6b58a42023-03-15 14:11:00 -0700347 commands.append('gdb-remote {}'.format(str(port)))
Haibo Huange194fce2020-01-06 14:40:27 -0800348 return '\n'.join(commands)
349
350
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000351def 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 -0800352 # Generate a setup script.
Alex Light92476652019-01-17 11:18:48 -0800353 root = os.environ["ANDROID_BUILD_TOP"]
354 symbols_dir = os.path.join(sysroot, "system", "lib64" if is64bit else "lib")
355 vendor_dir = os.path.join(sysroot, "vendor", "lib64" if is64bit else "lib")
356
357 solib_search_path = []
358 symbols_paths = ["", "hw", "ssl/engines", "drm", "egl", "soundfx"]
359 vendor_paths = ["", "hw", "egl"]
360 solib_search_path += [os.path.join(symbols_dir, x) for x in symbols_paths]
361 solib_search_path += [os.path.join(vendor_dir, x) for x in vendor_paths]
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700362 if linker_search_dir is not None:
363 solib_search_path += [linker_search_dir]
Alex Light92476652019-01-17 11:18:48 -0800364
Alex Lighta8f224d2020-11-10 10:30:19 -0800365 if debugger == "vscode-lldb":
366 return generate_vscode_lldb_script(
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000367 root, sysroot, binary_name, port, solib_search_path, vscode_launch_props)
Haibo Huange194fce2020-01-06 14:40:27 -0800368 elif debugger == 'lldb':
369 return generate_lldb_script(
Nikita Putikhin516960e2023-05-31 21:57:38 +0000370 root, sysroot, binary_name, port, solib_search_path)
Alex Light92476652019-01-17 11:18:48 -0800371 else:
372 raise Exception("Unknown debugger type " + debugger)
373
Josh Gao043bad72015-09-22 11:43:08 -0700374
Nikita Putikhin7da179e2023-07-03 07:37:58 +0000375def insert_commands_into_vscode_config(dst_launch_config: str, setup_commands: str) -> str:
376 """Inserts setup commands into launch config between two marker lines.
377 Marker lines are set in global variables g_vscode_config_marker_end and g_vscode_config_marker_end.
378 The commands are inserted with the same indentation as the first marker line.
379
380 Args:
381 dst_launch_config: Config to insert commands into.
382 setup_commands: Commands to insert.
383 Returns:
384 Config with inserted commands.
385 Raises:
386 ValueError if the begin marker is not found or not terminated with an end marker.
387 """
388
389 # We expect the files to be small (~10s KB), so we use simple string concatenation
390 # for simplicity and readability even if it is slower.
391 output = ""
392 found_at_least_one_begin = False
393 unterminated_begin_line = None
394
395 # It might be tempting to rewrite this using find() or even regexes,
396 # but keeping track of line numbers, preserving whitespace, and detecting indent
397 # becomes tricky enough that this simple loop is more clear.
398 for linenum, line in enumerate(dst_launch_config.splitlines(keepends=True), start=1):
399 if unterminated_begin_line != None:
400 if line.strip() == g_vscode_config_marker_end:
401 unterminated_begin_line = None
402 else:
403 continue
404 output += line
405 if line.strip() == g_vscode_config_marker_begin:
406 found_at_least_one_begin = True
407 unterminated_begin_line = linenum
408 marker_indent = line[:line.find(g_vscode_config_marker_begin)]
409 output += textwrap.indent(setup_commands, marker_indent) + '\n'
410
411 if not found_at_least_one_begin:
412 raise ValueError(f"Did not find begin marker line '{g_vscode_config_marker_begin}' " +
413 "in the VSCode launch file")
414
415 if unterminated_begin_line is not None:
416 raise ValueError(f"Unterminated begin marker at line {unterminated_begin_line} " +
417 f"in the VSCode launch file. Add end marker line to file: '{g_vscode_config_marker_end}'")
418
419 return output
420
421
422def replace_file_contents(dst_path: os.PathLike, contents: str) -> None:
423 """Replaces the contents of the file pointed to by dst_path.
424
425 This function writes the new contents into a temporary file, then atomically swaps it with
426 the target file. This way if a write fails, the original file is not overwritten.
427
428 Args:
429 dst_path: The path to the file to be replaced.
430 contents: The new contents of the file.
431 Raises:
432 Forwards exceptions from underlying filesystem methods.
433 """
434 tempf = tempfile.NamedTemporaryFile('w', delete=False)
435 try:
436 tempf.write(contents)
437 os.replace(tempf.name, dst_path)
438 except:
439 os.remove(tempf.name)
440 raise
441
442
443def write_vscode_config(vscode_launch_file: pathlib.Path, setup_commands: str) -> None:
444 """Writes setup_commands into the file pointed by vscode_launch_file.
445
446 See insert_commands_into_vscode_config for the description of how the setup commands are written.
447 """
448 contents = insert_commands_into_vscode_config(vscode_launch_file.read_text(), setup_commands)
449 replace_file_contents(vscode_launch_file, contents)
450
451
Nikita Putikhin516960e2023-05-31 21:57:38 +0000452def do_main() -> None:
Josh Gao466e2892017-07-13 15:39:05 -0700453 required_env = ["ANDROID_BUILD_TOP",
454 "ANDROID_PRODUCT_OUT", "TARGET_PRODUCT"]
455 for env in required_env:
456 if env not in os.environ:
457 sys.exit(
458 "Environment variable '{}' not defined, have you run lunch?".format(env))
459
Josh Gao043bad72015-09-22 11:43:08 -0700460 args = parse_args()
461 device = args.device
Josh Gao44b84a82015-10-28 11:57:37 -0700462
463 if device is None:
464 sys.exit("ERROR: Failed to find device.")
465
Josh Gao043bad72015-09-22 11:43:08 -0700466 root = os.environ["ANDROID_BUILD_TOP"]
Josh Gao466e2892017-07-13 15:39:05 -0700467 sysroot = os.path.join(os.environ["ANDROID_PRODUCT_OUT"], "symbols")
Josh Gao043bad72015-09-22 11:43:08 -0700468
Nikita Putikhincfff4e32024-04-22 13:04:45 +0200469 if args.cwd:
470 if not get_device_dir_exists(device, args.cwd):
471 raise ValueError('Working directory does not exist on device: {}'.format(args.cwd))
472 if args.chroot:
473 # See the comment in start_gdbserver about why this is not implemented.
474 raise ValueError('--cwd and --chroot cannot be used together')
475
Josh Gao043bad72015-09-22 11:43:08 -0700476 # Make sure the environment matches the attached device.
Peter Collingbourneba548262022-03-03 12:17:43 -0800477 # Skip when running in a chroot because the chroot lunch target may not
478 # match the device's lunch target.
479 if not args.chroot:
Nikita Putikhin516960e2023-05-31 21:57:38 +0000480 verify_device(device)
Josh Gao043bad72015-09-22 11:43:08 -0700481
482 debug_socket = "/data/local/tmp/debug_socket"
483 pid = None
484 run_cmd = None
485
486 # Fetch binary for -p, -n.
David Pursell639d1c42015-10-20 15:38:32 -0700487 binary_file, pid, run_cmd = handle_switches(args, sysroot)
Josh Gao043bad72015-09-22 11:43:08 -0700488
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000489 vscode_launch_props = None
490 if args.vscode_launch_props:
491 if args.setup_forwarding != "vscode-lldb":
Nikita Putikhin7da179e2023-07-03 07:37:58 +0000492 raise ValueError(
493 'vscode-launch-props requires --setup-forwarding=vscode-lldb')
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000494 vscode_launch_props = json.loads(args.vscode_launch_props)
495
Nikita Putikhin7da179e2023-07-03 07:37:58 +0000496 vscode_launch_file = None
497 if args.vscode_launch_file:
498 if args.setup_forwarding != "vscode-lldb":
499 raise ValueError(
500 'vscode-launch-file requires --setup-forwarding=vscode-lldb')
501 vscode_launch_file = args.vscode_launch_file
502
Josh Gao043bad72015-09-22 11:43:08 -0700503 with binary_file:
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700504 if sys.platform.startswith("linux"):
505 platform_name = "linux-x86"
506 elif sys.platform.startswith("darwin"):
507 platform_name = "darwin-x86"
508 else:
509 sys.exit("Unknown platform: {}".format(sys.platform))
510
Josh Gao043bad72015-09-22 11:43:08 -0700511 arch = gdbrunner.get_binary_arch(binary_file)
512 is64bit = arch.endswith("64")
513
514 # Make sure we have the linker
Pirama Arumuga Nainarf7f95442021-06-30 13:31:41 -0700515 clang_base = 'prebuilts/clang/host'
516 clang_version = read_toolchain_config(root)
Haibo Huange194fce2020-01-06 14:40:27 -0800517 toolchain_path = os.path.join(root, clang_base, platform_name,
518 clang_version)
519 llvm_readobj_path = os.path.join(toolchain_path, "bin", "llvm-readobj")
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700520 interp = gdbrunner.get_binary_interp(binary_file.name, llvm_readobj_path)
521 linker_search_dir = ensure_linker(device, sysroot, interp)
Josh Gao043bad72015-09-22 11:43:08 -0700522
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700523 tracer_pid = get_tracer_pid(device, pid)
524 if tracer_pid == 0:
Peter Collingbourne63bf1082018-12-19 20:51:42 -0800525 cmd_prefix = args.su_cmd
526 if args.env:
527 cmd_prefix += ['env'] + [v[0] for v in args.env]
528
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700529 # Start lldb-server.
530 server_local_path = get_lldb_server_path(root, clang_base, clang_version, arch)
531 server_remote_path = "/data/local/tmp/{}-lldb-server".format(arch)
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700532 gdbrunner.start_gdbserver(
Haibo Huange194fce2020-01-06 14:40:27 -0800533 device, server_local_path, server_remote_path,
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700534 target_pid=pid, run_cmd=run_cmd, debug_socket=debug_socket,
Nikita Putikhincfff4e32024-04-22 13:04:45 +0200535 port=args.port, run_as_cmd=cmd_prefix, lldb=True, chroot=args.chroot, cwd=args.cwd)
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700536 else:
Haibo Huange194fce2020-01-06 14:40:27 -0800537 print(
538 "Connecting to tracing pid {} using local port {}".format(
539 tracer_pid, args.port))
Elliott Hughes89e1ecf2017-06-30 14:03:32 -0700540 gdbrunner.forward_gdbserver_port(device, local=args.port,
541 remote="tcp:{}".format(args.port))
Josh Gao043bad72015-09-22 11:43:08 -0700542
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700543 debugger_path = get_lldb_path(toolchain_path)
544 debugger = args.setup_forwarding or 'lldb'
Haibo Huange194fce2020-01-06 14:40:27 -0800545
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700546 # Generate the lldb script.
Nikita Putikhin516960e2023-05-31 21:57:38 +0000547 setup_commands = generate_setup_script(sysroot=sysroot,
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700548 linker_search_dir=linker_search_dir,
Nikita Putikhin516960e2023-05-31 21:57:38 +0000549 binary_name=binary_file.name,
Alex Light92476652019-01-17 11:18:48 -0800550 is64bit=is64bit,
551 port=args.port,
Nikita Putikhin9aa3bc62023-05-19 20:39:51 +0000552 debugger=debugger,
553 vscode_launch_props=vscode_launch_props)
Josh Gao043bad72015-09-22 11:43:08 -0700554
Alex Lighta8f224d2020-11-10 10:30:19 -0800555 if not args.setup_forwarding:
Alex Light92476652019-01-17 11:18:48 -0800556 # Print a newline to separate our messages from the GDB session.
557 print("")
David Pursell639d1c42015-10-20 15:38:32 -0700558
Elliott Hughes4c8e8752021-06-25 14:23:22 -0700559 # Start lldb.
560 gdbrunner.start_gdb(debugger_path, setup_commands, lldb=True)
Alex Light92476652019-01-17 11:18:48 -0800561 else:
Nikita Putikhin7da179e2023-07-03 07:37:58 +0000562 if args.setup_forwarding == "vscode-lldb" and vscode_launch_file:
563 write_vscode_config(pathlib.Path(vscode_launch_file) , setup_commands)
564 print(f"Generated config written to '{vscode_launch_file}'")
Alex Light92476652019-01-17 11:18:48 -0800565 else:
Nikita Putikhin7da179e2023-07-03 07:37:58 +0000566 print("")
567 print(setup_commands)
568 print("")
569 if args.setup_forwarding == "vscode-lldb":
570 print(textwrap.dedent("""
571 Paste the above json into .vscode/launch.json and start the debugger as
572 normal."""))
573 else:
574 print(textwrap.dedent("""
575 Paste the lldb commands above into the lldb frontend to set up the
576 lldb-server connection."""))
577
578 print(textwrap.dedent("""
579 Press enter in this terminal once debugging is finished to shut lldb-server
580 down and close all the ports."""))
Alex Light92476652019-01-17 11:18:48 -0800581 print("")
Siarhei Vishniakou9c4f1b32021-12-02 10:43:14 -0800582 input("Press enter to shut down lldb-server")
Josh Gao043bad72015-09-22 11:43:08 -0700583
Ryan Prichard5d1c3cb2019-06-04 16:35:02 -0700584
585def main():
586 try:
587 do_main()
588 finally:
589 global g_temp_dirs
590 for temp_dir in g_temp_dirs:
591 shutil.rmtree(temp_dir)
592
593
Josh Gao043bad72015-09-22 11:43:08 -0700594if __name__ == "__main__":
595 main()