1#!/usr/bin/env python 2 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" just 7# import it with the full path using the "command script import" 8# command 9# (lldb) command script import /path/to/clandiag.py 10#---------------------------------------------------------------------- 11 12from __future__ import absolute_import, division, print_function 13import lldb 14import argparse 15import shlex 16import os 17import re 18import subprocess 19 20class MyParser(argparse.ArgumentParser): 21 def format_help(self): 22 return ''' Commands for managing clang diagnostic breakpoints 23 24Syntax: clangdiag enable [<warning>|<diag-name>] 25 clangdiag disable 26 clangdiag diagtool [<path>|reset] 27 28The following subcommands are supported: 29 30 enable -- Enable clang diagnostic breakpoints. 31 disable -- Disable all clang diagnostic breakpoints. 32 diagtool -- Return, set, or reset diagtool path. 33 34This command sets breakpoints in clang, and clang based tools, that 35emit diagnostics. When a diagnostic is emitted, and clangdiag is 36enabled, it will use the appropriate diagtool application to determine 37the name of the DiagID, and set breakpoints in all locations that 38'diag::name' appears in the source. Since the new breakpoints are set 39after they are encountered, users will need to launch the executable a 40second time in order to hit the new breakpoints. 41 42For in-tree builds, the diagtool application, used to map DiagID's to 43names, is found automatically in the same directory as the target 44executable. However, out-or-tree builds must use the 'diagtool' 45subcommand to set the appropriate path for diagtool in the clang debug 46bin directory. Since this mapping is created at build-time, it's 47important for users to use the same version that was generated when 48clang was compiled, or else the id's won't match. 49 50Notes: 51- Substrings can be passed for both <warning> and <diag-name>. 52- If <warning> is passed, only enable the DiagID(s) for that warning. 53- If <diag-name> is passed, only enable that DiagID. 54- Rerunning enable clears existing breakpoints. 55- diagtool is used in breakpoint callbacks, so it can be changed 56 without the need to rerun enable. 57- Adding this to your ~.lldbinit file makes clangdiag available at startup: 58 "command script import /path/to/clangdiag.py" 59 60''' 61 62def create_diag_options(): 63 parser = MyParser(prog='clangdiag') 64 subparsers = parser.add_subparsers( 65 title='subcommands', 66 dest='subcommands', 67 metavar='') 68 disable_parser = subparsers.add_parser('disable') 69 enable_parser = subparsers.add_parser('enable') 70 enable_parser.add_argument('id', nargs='?') 71 diagtool_parser = subparsers.add_parser('diagtool') 72 diagtool_parser.add_argument('path', nargs='?') 73 return parser 74 75def getDiagtool(target, diagtool = None): 76 id = target.GetProcess().GetProcessID() 77 if 'diagtool' not in getDiagtool.__dict__: 78 getDiagtool.diagtool = {} 79 if diagtool: 80 if diagtool == 'reset': 81 getDiagtool.diagtool[id] = None 82 elif os.path.exists(diagtool): 83 getDiagtool.diagtool[id] = diagtool 84 else: 85 print('clangdiag: %s not found.' % diagtool) 86 if not id in getDiagtool.diagtool or not getDiagtool.diagtool[id]: 87 getDiagtool.diagtool[id] = None 88 exe = target.GetExecutable() 89 if not exe.Exists(): 90 print('clangdiag: Target (%s) not set.' % exe.GetFilename()) 91 else: 92 diagtool = os.path.join(exe.GetDirectory(), 'diagtool') 93 if os.path.exists(diagtool): 94 getDiagtool.diagtool[id] = diagtool 95 else: 96 print('clangdiag: diagtool not found along side %s' % exe) 97 98 return getDiagtool.diagtool[id] 99 100def setDiagBreakpoint(frame, bp_loc, dict): 101 id = frame.FindVariable("DiagID").GetValue() 102 if id is None: 103 print('clangdiag: id is None') 104 return False 105 106 # Don't need to test this time, since we did that in enable. 107 target = frame.GetThread().GetProcess().GetTarget() 108 diagtool = getDiagtool(target) 109 name = subprocess.check_output([diagtool, "find-diagnostic-id", id]).rstrip(); 110 # Make sure we only consider errors, warnings, and extensions. 111 # FIXME: Make this configurable? 112 prefixes = ['err_', 'warn_', 'exp_'] 113 if len([prefix for prefix in prefixes+[''] if name.startswith(prefix)][0]): 114 bp = target.BreakpointCreateBySourceRegex(name, lldb.SBFileSpec()) 115 bp.AddName("clang::Diagnostic") 116 117 return False 118 119def enable(exe_ctx, args): 120 # Always disable existing breakpoints 121 disable(exe_ctx) 122 123 target = exe_ctx.GetTarget() 124 numOfBreakpoints = target.GetNumBreakpoints() 125 126 if args.id: 127 # Make sure we only consider errors, warnings, and extensions. 128 # FIXME: Make this configurable? 129 prefixes = ['err_', 'warn_', 'exp_'] 130 if len([prefix for prefix in prefixes+[''] if args.id.startswith(prefix)][0]): 131 bp = target.BreakpointCreateBySourceRegex(args.id, lldb.SBFileSpec()) 132 bp.AddName("clang::Diagnostic") 133 else: 134 diagtool = getDiagtool(target) 135 list = subprocess.check_output([diagtool, "list-warnings"]).rstrip(); 136 for line in list.splitlines(True): 137 m = re.search(r' *(.*) .*\[\-W' + re.escape(args.id) + r'.*].*', line) 138 # Make sure we only consider warnings. 139 if m and m.group(1).startswith('warn_'): 140 bp = target.BreakpointCreateBySourceRegex(m.group(1), lldb.SBFileSpec()) 141 bp.AddName("clang::Diagnostic") 142 else: 143 print('Adding callbacks.') 144 bp = target.BreakpointCreateByName('DiagnosticsEngine::Report') 145 bp.SetScriptCallbackFunction('clangdiag.setDiagBreakpoint') 146 bp.AddName("clang::Diagnostic") 147 148 count = target.GetNumBreakpoints() - numOfBreakpoints 149 print('%i breakpoint%s added.' % (count, "s"[count==1:])) 150 151 return 152 153def disable(exe_ctx): 154 target = exe_ctx.GetTarget() 155 # Remove all diag breakpoints. 156 bkpts = lldb.SBBreakpointList(target) 157 target.FindBreakpointsByName("clang::Diagnostic", bkpts) 158 for i in range(bkpts.GetSize()): 159 target.BreakpointDelete(bkpts.GetBreakpointAtIndex(i).GetID()) 160 161 return 162 163def the_diag_command(debugger, command, exe_ctx, result, dict): 164 # Use the Shell Lexer to properly parse up command options just like a 165 # shell would 166 command_args = shlex.split(command) 167 parser = create_diag_options() 168 try: 169 args = parser.parse_args(command_args) 170 except: 171 return 172 173 if args.subcommands == 'enable': 174 enable(exe_ctx, args) 175 elif args.subcommands == 'disable': 176 disable(exe_ctx) 177 else: 178 diagtool = getDiagtool(exe_ctx.GetTarget(), args.path) 179 print('diagtool = %s' % diagtool) 180 181 return 182 183def __lldb_init_module(debugger, dict): 184 # This initializer is being run from LLDB in the embedded command interpreter 185 # Make the options so we can generate the help text for the new LLDB 186 # command line command prior to registering it with LLDB below 187 parser = create_diag_options() 188 the_diag_command.__doc__ = parser.format_help() 189 # Add any commands contained in this module to LLDB 190 debugger.HandleCommand( 191 'command script add -f clangdiag.the_diag_command clangdiag') 192 print('The "clangdiag" command has been installed, type "help clangdiag" or "clangdiag --help" for detailed help.') 193