|  | #!/usr/bin/env python | 
|  |  | 
|  | # ---------------------------------------------------------------------- | 
|  | # Be sure to add the python path that points to the LLDB shared library. | 
|  | # | 
|  | # # To use this in the embedded python interpreter using "lldb" just | 
|  | # import it with the full path using the "command script import" | 
|  | # command | 
|  | #   (lldb) command script import /path/to/clandiag.py | 
|  | # ---------------------------------------------------------------------- | 
|  |  | 
|  | from __future__ import absolute_import, division, print_function | 
|  | import lldb | 
|  | import argparse | 
|  | import shlex | 
|  | import os | 
|  | import re | 
|  | import subprocess | 
|  |  | 
|  |  | 
|  | class MyParser(argparse.ArgumentParser): | 
|  | def format_help(self): | 
|  | return """     Commands for managing clang diagnostic breakpoints | 
|  |  | 
|  | Syntax: clangdiag enable [<warning>|<diag-name>] | 
|  | clangdiag disable | 
|  | clangdiag diagtool [<path>|reset] | 
|  |  | 
|  | The following subcommands are supported: | 
|  |  | 
|  | enable   -- Enable clang diagnostic breakpoints. | 
|  | disable  -- Disable all clang diagnostic breakpoints. | 
|  | diagtool -- Return, set, or reset diagtool path. | 
|  |  | 
|  | This command sets breakpoints in clang, and clang based tools, that | 
|  | emit diagnostics.  When a diagnostic is emitted, and clangdiag is | 
|  | enabled, it will use the appropriate diagtool application to determine | 
|  | the name of the DiagID, and set breakpoints in all locations that | 
|  | 'diag::name' appears in the source.  Since the new breakpoints are set | 
|  | after they are encountered, users will need to launch the executable a | 
|  | second time in order to hit the new breakpoints. | 
|  |  | 
|  | For in-tree builds, the diagtool application, used to map DiagID's to | 
|  | names, is found automatically in the same directory as the target | 
|  | executable.  However, out-or-tree builds must use the 'diagtool' | 
|  | subcommand to set the appropriate path for diagtool in the clang debug | 
|  | bin directory.  Since this mapping is created at build-time, it's | 
|  | important for users to use the same version that was generated when | 
|  | clang was compiled, or else the id's won't match. | 
|  |  | 
|  | Notes: | 
|  | - Substrings can be passed for both <warning> and <diag-name>. | 
|  | - If <warning> is passed, only enable the DiagID(s) for that warning. | 
|  | - If <diag-name> is passed, only enable that DiagID. | 
|  | - Rerunning enable clears existing breakpoints. | 
|  | - diagtool is used in breakpoint callbacks, so it can be changed | 
|  | without the need to rerun enable. | 
|  | - Adding this to your ~.lldbinit file makes clangdiag available at startup: | 
|  | "command script import /path/to/clangdiag.py" | 
|  |  | 
|  | """ | 
|  |  | 
|  |  | 
|  | def create_diag_options(): | 
|  | parser = MyParser(prog="clangdiag") | 
|  | subparsers = parser.add_subparsers( | 
|  | title="subcommands", dest="subcommands", metavar="" | 
|  | ) | 
|  | disable_parser = subparsers.add_parser("disable") | 
|  | enable_parser = subparsers.add_parser("enable") | 
|  | enable_parser.add_argument("id", nargs="?") | 
|  | diagtool_parser = subparsers.add_parser("diagtool") | 
|  | diagtool_parser.add_argument("path", nargs="?") | 
|  | return parser | 
|  |  | 
|  |  | 
|  | def getDiagtool(target, diagtool=None): | 
|  | id = target.GetProcess().GetProcessID() | 
|  | if "diagtool" not in getDiagtool.__dict__: | 
|  | getDiagtool.diagtool = {} | 
|  | if diagtool: | 
|  | if diagtool == "reset": | 
|  | getDiagtool.diagtool[id] = None | 
|  | elif os.path.exists(diagtool): | 
|  | getDiagtool.diagtool[id] = diagtool | 
|  | else: | 
|  | print("clangdiag: %s not found." % diagtool) | 
|  | if not id in getDiagtool.diagtool or not getDiagtool.diagtool[id]: | 
|  | getDiagtool.diagtool[id] = None | 
|  | exe = target.GetExecutable() | 
|  | if not exe.Exists(): | 
|  | print("clangdiag: Target (%s) not set." % exe.GetFilename()) | 
|  | else: | 
|  | diagtool = os.path.join(exe.GetDirectory(), "diagtool") | 
|  | if os.path.exists(diagtool): | 
|  | getDiagtool.diagtool[id] = diagtool | 
|  | else: | 
|  | print("clangdiag: diagtool not found along side %s" % exe) | 
|  |  | 
|  | return getDiagtool.diagtool[id] | 
|  |  | 
|  |  | 
|  | def setDiagBreakpoint(frame, bp_loc, dict): | 
|  | id = frame.FindVariable("DiagID").GetValue() | 
|  | if id is None: | 
|  | print("clangdiag: id is None") | 
|  | return False | 
|  |  | 
|  | # Don't need to test this time, since we did that in enable. | 
|  | target = frame.GetThread().GetProcess().GetTarget() | 
|  | diagtool = getDiagtool(target) | 
|  | name = subprocess.check_output([diagtool, "find-diagnostic-id", id]).rstrip() | 
|  | # Make sure we only consider errors, warnings, and extensions. | 
|  | # FIXME: Make this configurable? | 
|  | prefixes = ["err_", "warn_", "exp_"] | 
|  | if len([prefix for prefix in prefixes + [""] if name.startswith(prefix)][0]): | 
|  | bp = target.BreakpointCreateBySourceRegex(name, lldb.SBFileSpec()) | 
|  | bp.AddName("clang::Diagnostic") | 
|  |  | 
|  | return False | 
|  |  | 
|  |  | 
|  | def enable(exe_ctx, args): | 
|  | # Always disable existing breakpoints | 
|  | disable(exe_ctx) | 
|  |  | 
|  | target = exe_ctx.GetTarget() | 
|  | numOfBreakpoints = target.GetNumBreakpoints() | 
|  |  | 
|  | if args.id: | 
|  | # Make sure we only consider errors, warnings, and extensions. | 
|  | # FIXME: Make this configurable? | 
|  | prefixes = ["err_", "warn_", "exp_"] | 
|  | if len([prefix for prefix in prefixes + [""] if args.id.startswith(prefix)][0]): | 
|  | bp = target.BreakpointCreateBySourceRegex(args.id, lldb.SBFileSpec()) | 
|  | bp.AddName("clang::Diagnostic") | 
|  | else: | 
|  | diagtool = getDiagtool(target) | 
|  | list = subprocess.check_output([diagtool, "list-warnings"]).rstrip() | 
|  | for line in list.splitlines(True): | 
|  | m = re.search(r" *(.*) .*\[\-W" + re.escape(args.id) + r".*].*", line) | 
|  | # Make sure we only consider warnings. | 
|  | if m and m.group(1).startswith("warn_"): | 
|  | bp = target.BreakpointCreateBySourceRegex( | 
|  | m.group(1), lldb.SBFileSpec() | 
|  | ) | 
|  | bp.AddName("clang::Diagnostic") | 
|  | else: | 
|  | print("Adding callbacks.") | 
|  | bp = target.BreakpointCreateByName("DiagnosticsEngine::Report") | 
|  | bp.SetScriptCallbackFunction("clangdiag.setDiagBreakpoint") | 
|  | bp.AddName("clang::Diagnostic") | 
|  |  | 
|  | count = target.GetNumBreakpoints() - numOfBreakpoints | 
|  | print("%i breakpoint%s added." % (count, "s"[count == 1 :])) | 
|  |  | 
|  | return | 
|  |  | 
|  |  | 
|  | def disable(exe_ctx): | 
|  | target = exe_ctx.GetTarget() | 
|  | # Remove all diag breakpoints. | 
|  | bkpts = lldb.SBBreakpointList(target) | 
|  | target.FindBreakpointsByName("clang::Diagnostic", bkpts) | 
|  | for i in range(bkpts.GetSize()): | 
|  | target.BreakpointDelete(bkpts.GetBreakpointAtIndex(i).GetID()) | 
|  |  | 
|  | return | 
|  |  | 
|  |  | 
|  | def the_diag_command(debugger, command, exe_ctx, result, dict): | 
|  | # Use the Shell Lexer to properly parse up command options just like a | 
|  | # shell would | 
|  | command_args = shlex.split(command) | 
|  | parser = create_diag_options() | 
|  | try: | 
|  | args = parser.parse_args(command_args) | 
|  | except: | 
|  | return | 
|  |  | 
|  | if args.subcommands == "enable": | 
|  | enable(exe_ctx, args) | 
|  | elif args.subcommands == "disable": | 
|  | disable(exe_ctx) | 
|  | else: | 
|  | diagtool = getDiagtool(exe_ctx.GetTarget(), args.path) | 
|  | print("diagtool = %s" % diagtool) | 
|  |  | 
|  | return | 
|  |  | 
|  |  | 
|  | def __lldb_init_module(debugger, dict): | 
|  | # This initializer is being run from LLDB in the embedded command interpreter | 
|  | # Make the options so we can generate the help text for the new LLDB | 
|  | # command line command prior to registering it with LLDB below | 
|  | parser = create_diag_options() | 
|  | the_diag_command.__doc__ = parser.format_help() | 
|  | # Add any commands contained in this module to LLDB | 
|  | debugger.HandleCommand("command script add -f clangdiag.the_diag_command clangdiag") | 
|  | print( | 
|  | 'The "clangdiag" command has been installed, type "help clangdiag" or "clangdiag --help" for detailed help.' | 
|  | ) |