brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.5 KiB · 8c47b1c Raw
203 lines · python
1#!/usr/bin/env python2 3# ----------------------------------------------------------------------4# Be sure to add the python path that points to the LLDB shared library.5#6# # To use this in the embedded python interpreter using "lldb" just7# import it with the full path using the "command script import"8# command9#   (lldb) command script import /path/to/clandiag.py10# ----------------------------------------------------------------------11 12from __future__ import absolute_import, division, print_function13import lldb14import argparse15import shlex16import os17import re18import subprocess19 20 21class MyParser(argparse.ArgumentParser):22    def format_help(self):23        return """     Commands for managing clang diagnostic breakpoints24 25Syntax: clangdiag enable [<warning>|<diag-name>]26        clangdiag disable27        clangdiag diagtool [<path>|reset]28 29The following subcommands are supported:30 31      enable   -- Enable clang diagnostic breakpoints.32      disable  -- Disable all clang diagnostic breakpoints.33      diagtool -- Return, set, or reset diagtool path.34 35This command sets breakpoints in clang, and clang based tools, that36emit diagnostics.  When a diagnostic is emitted, and clangdiag is37enabled, it will use the appropriate diagtool application to determine38the name of the DiagID, and set breakpoints in all locations that39'diag::name' appears in the source.  Since the new breakpoints are set40after they are encountered, users will need to launch the executable a41second time in order to hit the new breakpoints.42 43For in-tree builds, the diagtool application, used to map DiagID's to44names, is found automatically in the same directory as the target45executable.  However, out-or-tree builds must use the 'diagtool'46subcommand to set the appropriate path for diagtool in the clang debug47bin directory.  Since this mapping is created at build-time, it's48important for users to use the same version that was generated when49clang was compiled, or else the id's won't match.50 51Notes:52- Substrings can be passed for both <warning> and <diag-name>.53- If <warning> is passed, only enable the DiagID(s) for that warning.54- If <diag-name> is passed, only enable that DiagID.55- Rerunning enable clears existing breakpoints.56- diagtool is used in breakpoint callbacks, so it can be changed57  without the need to rerun enable.58- Adding this to your ~.lldbinit file makes clangdiag available at startup:59  "command script import /path/to/clangdiag.py"60 61"""62 63 64def create_diag_options():65    parser = MyParser(prog="clangdiag")66    subparsers = parser.add_subparsers(67        title="subcommands", dest="subcommands", metavar=""68    )69    disable_parser = subparsers.add_parser("disable")70    enable_parser = subparsers.add_parser("enable")71    enable_parser.add_argument("id", nargs="?")72    diagtool_parser = subparsers.add_parser("diagtool")73    diagtool_parser.add_argument("path", nargs="?")74    return parser75 76 77def getDiagtool(target, diagtool=None):78    id = target.GetProcess().GetProcessID()79    if "diagtool" not in getDiagtool.__dict__:80        getDiagtool.diagtool = {}81    if diagtool:82        if diagtool == "reset":83            getDiagtool.diagtool[id] = None84        elif os.path.exists(diagtool):85            getDiagtool.diagtool[id] = diagtool86        else:87            print("clangdiag: %s not found." % diagtool)88    if not id in getDiagtool.diagtool or not getDiagtool.diagtool[id]:89        getDiagtool.diagtool[id] = None90        exe = target.GetExecutable()91        if not exe.Exists():92            print("clangdiag: Target (%s) not set." % exe.GetFilename())93        else:94            diagtool = os.path.join(exe.GetDirectory(), "diagtool")95            if os.path.exists(diagtool):96                getDiagtool.diagtool[id] = diagtool97            else:98                print("clangdiag: diagtool not found along side %s" % exe)99 100    return getDiagtool.diagtool[id]101 102 103def setDiagBreakpoint(frame, bp_loc, dict):104    id = frame.FindVariable("DiagID").GetValue()105    if id is None:106        print("clangdiag: id is None")107        return False108 109    # Don't need to test this time, since we did that in enable.110    target = frame.GetThread().GetProcess().GetTarget()111    diagtool = getDiagtool(target)112    name = subprocess.check_output([diagtool, "find-diagnostic-id", id]).rstrip()113    # Make sure we only consider errors, warnings, and extensions.114    # FIXME: Make this configurable?115    prefixes = ["err_", "warn_", "exp_"]116    if len([prefix for prefix in prefixes + [""] if name.startswith(prefix)][0]):117        bp = target.BreakpointCreateBySourceRegex(name, lldb.SBFileSpec())118        bp.AddName("clang::Diagnostic")119 120    return False121 122 123def enable(exe_ctx, args):124    # Always disable existing breakpoints125    disable(exe_ctx)126 127    target = exe_ctx.GetTarget()128    numOfBreakpoints = target.GetNumBreakpoints()129 130    if args.id:131        # Make sure we only consider errors, warnings, and extensions.132        # FIXME: Make this configurable?133        prefixes = ["err_", "warn_", "exp_"]134        if len([prefix for prefix in prefixes + [""] if args.id.startswith(prefix)][0]):135            bp = target.BreakpointCreateBySourceRegex(args.id, lldb.SBFileSpec())136            bp.AddName("clang::Diagnostic")137        else:138            diagtool = getDiagtool(target)139            list = subprocess.check_output([diagtool, "list-warnings"]).rstrip()140            for line in list.splitlines(True):141                m = re.search(r" *(.*) .*\[\-W" + re.escape(args.id) + r".*].*", line)142                # Make sure we only consider warnings.143                if m and m.group(1).startswith("warn_"):144                    bp = target.BreakpointCreateBySourceRegex(145                        m.group(1), lldb.SBFileSpec()146                    )147                    bp.AddName("clang::Diagnostic")148    else:149        print("Adding callbacks.")150        bp = target.BreakpointCreateByName("DiagnosticsEngine::Report")151        bp.SetScriptCallbackFunction("clangdiag.setDiagBreakpoint")152        bp.AddName("clang::Diagnostic")153 154    count = target.GetNumBreakpoints() - numOfBreakpoints155    print("%i breakpoint%s added." % (count, "s"[count == 1 :]))156 157    return158 159 160def disable(exe_ctx):161    target = exe_ctx.GetTarget()162    # Remove all diag breakpoints.163    bkpts = lldb.SBBreakpointList(target)164    target.FindBreakpointsByName("clang::Diagnostic", bkpts)165    for i in range(bkpts.GetSize()):166        target.BreakpointDelete(bkpts.GetBreakpointAtIndex(i).GetID())167 168    return169 170 171def the_diag_command(debugger, command, exe_ctx, result, dict):172    # Use the Shell Lexer to properly parse up command options just like a173    # shell would174    command_args = shlex.split(command)175    parser = create_diag_options()176    try:177        args = parser.parse_args(command_args)178    except:179        return180 181    if args.subcommands == "enable":182        enable(exe_ctx, args)183    elif args.subcommands == "disable":184        disable(exe_ctx)185    else:186        diagtool = getDiagtool(exe_ctx.GetTarget(), args.path)187        print("diagtool = %s" % diagtool)188 189    return190 191 192def __lldb_init_module(debugger, dict):193    # This initializer is being run from LLDB in the embedded command interpreter194    # Make the options so we can generate the help text for the new LLDB195    # command line command prior to registering it with LLDB below196    parser = create_diag_options()197    the_diag_command.__doc__ = parser.format_help()198    # Add any commands contained in this module to LLDB199    debugger.HandleCommand("command script add -f clangdiag.the_diag_command clangdiag")200    print(201        'The "clangdiag" command has been installed, type "help clangdiag" or "clangdiag --help" for detailed help.'202    )203