brintos

brintos / llvm-project-archived public Read only

0
0
Text · 12.1 KiB · 0232bde Raw
363 lines · python
1# DExTer : Debugging Experience Tester2# ~~~~~~   ~         ~~         ~   ~~3#4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.5# See https://llvm.org/LICENSE.txt for license information.6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception7"""Discover potential/available debugger interfaces."""8 9from collections import OrderedDict10import os11import pickle12import platform13import subprocess14import sys15from tempfile import NamedTemporaryFile16 17from dex.command import get_command_infos18from dex.dextIR import DextIR19from dex.utils import get_root_directory, Timer20from dex.utils.Environment import is_native_windows21from dex.utils.Exceptions import ToolArgumentError22from dex.utils.Exceptions import DebuggerException23 24from dex.debugger.DebuggerControllers.DefaultController import DefaultController25 26from dex.debugger.dbgeng.dbgeng import DbgEng27from dex.debugger.lldb.LLDB import LLDB, LLDBDAP28from dex.debugger.visualstudio.VisualStudio2015 import VisualStudio201529from dex.debugger.visualstudio.VisualStudio2017 import VisualStudio201730from dex.debugger.visualstudio.VisualStudio2019 import VisualStudio201931from dex.debugger.visualstudio.VisualStudio2022 import VisualStudio202232 33 34def _get_potential_debuggers():  # noqa35    """Return a dict of the supported debuggers.36    Returns:37        { name (str): debugger (class) }38    """39    return {40        DbgEng.get_option_name(): DbgEng,41        LLDB.get_option_name(): LLDB,42        LLDBDAP.get_option_name(): LLDBDAP,43        VisualStudio2015.get_option_name(): VisualStudio2015,44        VisualStudio2017.get_option_name(): VisualStudio2017,45        VisualStudio2019.get_option_name(): VisualStudio2019,46        VisualStudio2022.get_option_name(): VisualStudio2022,47    }48 49 50def _warn_meaningless_option(context, option):51    if hasattr(context.options, "list_debuggers"):52        return53 54    context.logger.warning(55        f'option "{option}" is meaningless with this debugger',56        enable_prefix=True,57        flag=f"--debugger={context.options.debugger}",58    )59 60 61def add_debugger_tool_base_arguments(parser, defaults):62    defaults.lldb_executable = "lldb.exe" if is_native_windows() else "lldb"63    parser.add_argument(64        "--lldb-executable",65        type=str,66        metavar="<file>",67        default=None,68        display_default=defaults.lldb_executable,69        help="location of `lldb` executable for --debugger=lldb, or `lldb-dap` for --debugger=lldb-dap",70    )71    dap_group = parser.add_argument_group("DAP Debugger arguments")72    dap_group.add_argument(73        "--dap-message-log",74        type=str,75        metavar="<filepath>",76        default=None,77        help="log file for messages between Dexter and the debug adapter; set to '-' to log to stdout, '-e' to log to stderr",78    )79    dap_group.add_argument(80        "--colorize-dap-log",81        action="store_true",82        default=False,83        help="apply colors to the logged DAP messages",84    )85    dap_group.add_argument(86        "--format-dap-log",87        type=str,88        default="pretty",89        choices=["oneline", "pretty"],90    )91 92 93def add_debugger_tool_arguments(parser, context, defaults):94    debuggers = Debuggers(context)95    potential_debuggers = sorted(debuggers.potential_debuggers().keys())96 97    add_debugger_tool_base_arguments(parser, defaults)98 99    parser.add_argument(100        "--debugger",101        type=str,102        choices=potential_debuggers,103        required=True,104        help="debugger to use",105    )106    parser.add_argument(107        "--max-steps",108        metavar="<int>",109        type=int,110        default=1000,111        help="maximum number of program steps allowed",112    )113    parser.add_argument(114        "--pause-between-steps",115        metavar="<seconds>",116        type=float,117        default=0.0,118        help="number of seconds to pause between steps",119    )120    defaults.show_debugger = False121    parser.add_argument(122        "--show-debugger", action="store_true", default=None, help="show the debugger"123    )124    defaults.arch = platform.machine()125    parser.add_argument(126        "--arch",127        type=str,128        metavar="<architecture>",129        default=None,130        display_default=defaults.arch,131        help="target architecture",132    )133    defaults.source_root_dir = ""134    parser.add_argument(135        "--source-root-dir",136        type=str,137        metavar="<directory>",138        default=None,139        help="source root directory",140    )141    parser.add_argument(142        "--debugger-use-relative-paths",143        action="store_true",144        default=False,145        help="pass the debugger paths relative to --source-root-dir",146    )147    parser.add_argument(148        "--target-run-args",149        type=str,150        metavar="<flags>",151        default="",152        help="command line arguments for the test program, in addition to any "153        "provided by DexCommandLine",154    )155    parser.add_argument(156        "--timeout-total",157        metavar="<seconds>",158        type=float,159        default=0.0,160        help="if >0, debugger session will automatically exit after "161        "running for <timeout-total> seconds",162    )163    parser.add_argument(164        "--timeout-breakpoint",165        metavar="<seconds>",166        type=float,167        default=0.0,168        help="if >0, debugger session will automatically exit after "169        "waiting <timeout-breakpoint> seconds without hitting a "170        "breakpoint",171    )172 173 174def handle_debugger_tool_base_options(context, defaults):  # noqa175    options = context.options176 177    if options.lldb_executable is None:178        options.lldb_executable = defaults.lldb_executable179    else:180        if getattr(options, "debugger", "lldb") not in ("lldb", "lldb-dap"):181            _warn_meaningless_option(context, "--lldb-executable")182 183        options.lldb_executable = os.path.abspath(options.lldb_executable)184        if not os.path.isfile(options.lldb_executable):185            raise ToolArgumentError(186                '<d>could not find</> <r>"{}"</>'.format(options.lldb_executable)187            )188 189    if (190        options.dap_message_log is not None191        and options.dap_message_log != "-"192        and options.dap_message_log != "-e"193    ):194        options.dap_message_log = os.path.abspath(options.dap_message_log)195 196 197def handle_debugger_tool_options(context, defaults):  # noqa198    options = context.options199 200    handle_debugger_tool_base_options(context, defaults)201 202    if options.arch is None:203        options.arch = defaults.arch204    else:205        if options.debugger != "lldb":206            _warn_meaningless_option(context, "--arch")207 208    if options.show_debugger is None:209        options.show_debugger = defaults.show_debugger210    else:211        if options.debugger == "lldb":212            _warn_meaningless_option(context, "--show-debugger")213 214    if options.source_root_dir is not None:215        if not os.path.isabs(options.source_root_dir):216            raise ToolArgumentError(217                f'<d>--source-root-dir: expected absolute path, got</> <r>"{options.source_root_dir}"</>'218            )219        if not os.path.isdir(options.source_root_dir):220            raise ToolArgumentError(221                f'<d>--source-root-dir: could not find directory</> <r>"{options.source_root_dir}"</>'222            )223 224    if options.debugger_use_relative_paths:225        if not options.source_root_dir:226            raise ToolArgumentError(227                f"<d>--debugger-relative-paths</> <r>requires --source-root-dir</>"228            )229 230 231def run_debugger_subprocess(debugger_controller, working_dir_path):232    with NamedTemporaryFile(dir=working_dir_path, delete=False, mode="wb") as fp:233        pickle.dump(debugger_controller, fp, protocol=pickle.HIGHEST_PROTOCOL)234        controller_path = fp.name235 236    dexter_py = os.path.basename(sys.argv[0])237    if not os.path.isfile(dexter_py):238        dexter_py = os.path.join(get_root_directory(), "..", dexter_py)239    assert os.path.isfile(dexter_py)240 241    with NamedTemporaryFile(dir=working_dir_path) as fp:242        args = [243            sys.executable,244            dexter_py,245            "run-debugger-internal-",246            controller_path,247            "--working-directory={}".format(working_dir_path),248            "--unittest=off",249            "--indent-timer-level={}".format(Timer.indent + 2),250        ]251        try:252            with Timer("running external debugger process"):253                subprocess.check_call(args)254        except subprocess.CalledProcessError as e:255            raise DebuggerException(e)256 257    with open(controller_path, "rb") as fp:258        debugger_controller = pickle.load(fp)259    return debugger_controller260 261 262class Debuggers(object):263    @classmethod264    def potential_debuggers(cls):265        try:266            return cls._potential_debuggers267        except AttributeError:268            cls._potential_debuggers = _get_potential_debuggers()269            return cls._potential_debuggers270 271    def __init__(self, context):272        self.context = context273 274    def load(self, key):275        with Timer("load {}".format(key)):276            return Debuggers.potential_debuggers()[key](self.context)277 278    def _populate_debugger_cache(self):279        debuggers = []280        for key in sorted(Debuggers.potential_debuggers()):281            debugger = self.load(key)282 283            class LoadedDebugger(object):284                pass285 286            LoadedDebugger.option_name = key287            LoadedDebugger.full_name = "[{}]".format(debugger.name)288            LoadedDebugger.is_available = debugger.is_available289 290            if LoadedDebugger.is_available:291                try:292                    LoadedDebugger.version = debugger.version.splitlines()293                except AttributeError:294                    LoadedDebugger.version = [""]295            else:296                try:297                    LoadedDebugger.error = debugger.loading_error.splitlines()298                except AttributeError:299                    LoadedDebugger.error = [""]300 301                try:302                    LoadedDebugger.error_trace = debugger.loading_error_trace303                except AttributeError:304                    LoadedDebugger.error_trace = None305 306            debuggers.append(LoadedDebugger)307        return debuggers308 309    def list(self):310        debuggers = self._populate_debugger_cache()311 312        max_o_len = max(len(d.option_name) for d in debuggers)313        max_n_len = max(len(d.full_name) for d in debuggers)314 315        msgs = []316 317        for d in debuggers:318            # Option name, right padded with spaces for alignment319            option_name = "{{name: <{}}}".format(max_o_len).format(name=d.option_name)320 321            # Full name, right padded with spaces for alignment322            full_name = "{{name: <{}}}".format(max_n_len).format(name=d.full_name)323 324            if d.is_available:325                name = "<b>{} {}</>".format(option_name, full_name)326 327                # If the debugger is available, show the first line of the328                #  version info.329                available = "<g>YES</>"330                info = "<b>({})</>".format(d.version[0])331            else:332                name = "<y>{} {}</>".format(option_name, full_name)333 334                # If the debugger is not available, show the first line of the335                # error reason.336                available = "<r>NO</> "337                info = "<y>({})</>".format(d.error[0])338 339            msg = "{} {} {}".format(name, available, info)340 341            if self.context.options.verbose:342                # If verbose mode and there was more version or error output343                # than could be displayed in a single line, display the whole344                # lot slightly indented.345                verbose_info = None346                if d.is_available:347                    if d.version[1:]:348                        verbose_info = d.version + ["\n"]349                else:350                    # Some of list elems may contain multiple lines, so make351                    # sure each elem is a line of its own.352                    verbose_info = d.error_trace353 354                if verbose_info:355                    verbose_info = (356                        "\n".join("        {}".format(l.rstrip()) for l in verbose_info)357                        + "\n"358                    )359                    msg = "{}\n\n{}".format(msg, verbose_info)360 361            msgs.append(msg)362        self.context.o.auto("\n{}\n\n".format("\n".join(msgs)))363