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