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