1*e038c9c4Sjoerg#!/usr/bin/env python 27330f729Sjoerg 37330f729Sjoerg#---------------------------------------------------------------------- 47330f729Sjoerg# Be sure to add the python path that points to the LLDB shared library. 57330f729Sjoerg# 67330f729Sjoerg# # To use this in the embedded python interpreter using "lldb" just 77330f729Sjoerg# import it with the full path using the "command script import" 87330f729Sjoerg# command 97330f729Sjoerg# (lldb) command script import /path/to/clandiag.py 107330f729Sjoerg#---------------------------------------------------------------------- 117330f729Sjoerg 127330f729Sjoergfrom __future__ import absolute_import, division, print_function 137330f729Sjoergimport lldb 147330f729Sjoergimport argparse 157330f729Sjoergimport shlex 167330f729Sjoergimport os 177330f729Sjoergimport re 187330f729Sjoergimport subprocess 197330f729Sjoerg 207330f729Sjoergclass MyParser(argparse.ArgumentParser): 217330f729Sjoerg def format_help(self): 227330f729Sjoerg return ''' Commands for managing clang diagnostic breakpoints 237330f729Sjoerg 247330f729SjoergSyntax: clangdiag enable [<warning>|<diag-name>] 257330f729Sjoerg clangdiag disable 267330f729Sjoerg clangdiag diagtool [<path>|reset] 277330f729Sjoerg 287330f729SjoergThe following subcommands are supported: 297330f729Sjoerg 307330f729Sjoerg enable -- Enable clang diagnostic breakpoints. 317330f729Sjoerg disable -- Disable all clang diagnostic breakpoints. 327330f729Sjoerg diagtool -- Return, set, or reset diagtool path. 337330f729Sjoerg 347330f729SjoergThis command sets breakpoints in clang, and clang based tools, that 357330f729Sjoergemit diagnostics. When a diagnostic is emitted, and clangdiag is 367330f729Sjoergenabled, it will use the appropriate diagtool application to determine 377330f729Sjoergthe name of the DiagID, and set breakpoints in all locations that 387330f729Sjoerg'diag::name' appears in the source. Since the new breakpoints are set 397330f729Sjoergafter they are encountered, users will need to launch the executable a 407330f729Sjoergsecond time in order to hit the new breakpoints. 417330f729Sjoerg 427330f729SjoergFor in-tree builds, the diagtool application, used to map DiagID's to 437330f729Sjoergnames, is found automatically in the same directory as the target 447330f729Sjoergexecutable. However, out-or-tree builds must use the 'diagtool' 457330f729Sjoergsubcommand to set the appropriate path for diagtool in the clang debug 467330f729Sjoergbin directory. Since this mapping is created at build-time, it's 477330f729Sjoergimportant for users to use the same version that was generated when 487330f729Sjoergclang was compiled, or else the id's won't match. 497330f729Sjoerg 507330f729SjoergNotes: 517330f729Sjoerg- Substrings can be passed for both <warning> and <diag-name>. 527330f729Sjoerg- If <warning> is passed, only enable the DiagID(s) for that warning. 537330f729Sjoerg- If <diag-name> is passed, only enable that DiagID. 547330f729Sjoerg- Rerunning enable clears existing breakpoints. 557330f729Sjoerg- diagtool is used in breakpoint callbacks, so it can be changed 567330f729Sjoerg without the need to rerun enable. 577330f729Sjoerg- Adding this to your ~.lldbinit file makes clangdiag available at startup: 587330f729Sjoerg "command script import /path/to/clangdiag.py" 597330f729Sjoerg 607330f729Sjoerg''' 617330f729Sjoerg 627330f729Sjoergdef create_diag_options(): 637330f729Sjoerg parser = MyParser(prog='clangdiag') 647330f729Sjoerg subparsers = parser.add_subparsers( 657330f729Sjoerg title='subcommands', 667330f729Sjoerg dest='subcommands', 677330f729Sjoerg metavar='') 687330f729Sjoerg disable_parser = subparsers.add_parser('disable') 697330f729Sjoerg enable_parser = subparsers.add_parser('enable') 707330f729Sjoerg enable_parser.add_argument('id', nargs='?') 717330f729Sjoerg diagtool_parser = subparsers.add_parser('diagtool') 727330f729Sjoerg diagtool_parser.add_argument('path', nargs='?') 737330f729Sjoerg return parser 747330f729Sjoerg 757330f729Sjoergdef getDiagtool(target, diagtool = None): 767330f729Sjoerg id = target.GetProcess().GetProcessID() 777330f729Sjoerg if 'diagtool' not in getDiagtool.__dict__: 787330f729Sjoerg getDiagtool.diagtool = {} 797330f729Sjoerg if diagtool: 807330f729Sjoerg if diagtool == 'reset': 817330f729Sjoerg getDiagtool.diagtool[id] = None 827330f729Sjoerg elif os.path.exists(diagtool): 837330f729Sjoerg getDiagtool.diagtool[id] = diagtool 847330f729Sjoerg else: 857330f729Sjoerg print('clangdiag: %s not found.' % diagtool) 867330f729Sjoerg if not id in getDiagtool.diagtool or not getDiagtool.diagtool[id]: 877330f729Sjoerg getDiagtool.diagtool[id] = None 887330f729Sjoerg exe = target.GetExecutable() 897330f729Sjoerg if not exe.Exists(): 907330f729Sjoerg print('clangdiag: Target (%s) not set.' % exe.GetFilename()) 917330f729Sjoerg else: 927330f729Sjoerg diagtool = os.path.join(exe.GetDirectory(), 'diagtool') 937330f729Sjoerg if os.path.exists(diagtool): 947330f729Sjoerg getDiagtool.diagtool[id] = diagtool 957330f729Sjoerg else: 967330f729Sjoerg print('clangdiag: diagtool not found along side %s' % exe) 977330f729Sjoerg 987330f729Sjoerg return getDiagtool.diagtool[id] 997330f729Sjoerg 1007330f729Sjoergdef setDiagBreakpoint(frame, bp_loc, dict): 1017330f729Sjoerg id = frame.FindVariable("DiagID").GetValue() 1027330f729Sjoerg if id is None: 1037330f729Sjoerg print('clangdiag: id is None') 1047330f729Sjoerg return False 1057330f729Sjoerg 1067330f729Sjoerg # Don't need to test this time, since we did that in enable. 1077330f729Sjoerg target = frame.GetThread().GetProcess().GetTarget() 1087330f729Sjoerg diagtool = getDiagtool(target) 1097330f729Sjoerg name = subprocess.check_output([diagtool, "find-diagnostic-id", id]).rstrip(); 1107330f729Sjoerg # Make sure we only consider errors, warnings, and extensions. 1117330f729Sjoerg # FIXME: Make this configurable? 1127330f729Sjoerg prefixes = ['err_', 'warn_', 'exp_'] 1137330f729Sjoerg if len([prefix for prefix in prefixes+[''] if name.startswith(prefix)][0]): 1147330f729Sjoerg bp = target.BreakpointCreateBySourceRegex(name, lldb.SBFileSpec()) 1157330f729Sjoerg bp.AddName("clang::Diagnostic") 1167330f729Sjoerg 1177330f729Sjoerg return False 1187330f729Sjoerg 1197330f729Sjoergdef enable(exe_ctx, args): 1207330f729Sjoerg # Always disable existing breakpoints 1217330f729Sjoerg disable(exe_ctx) 1227330f729Sjoerg 1237330f729Sjoerg target = exe_ctx.GetTarget() 1247330f729Sjoerg numOfBreakpoints = target.GetNumBreakpoints() 1257330f729Sjoerg 1267330f729Sjoerg if args.id: 1277330f729Sjoerg # Make sure we only consider errors, warnings, and extensions. 1287330f729Sjoerg # FIXME: Make this configurable? 1297330f729Sjoerg prefixes = ['err_', 'warn_', 'exp_'] 1307330f729Sjoerg if len([prefix for prefix in prefixes+[''] if args.id.startswith(prefix)][0]): 1317330f729Sjoerg bp = target.BreakpointCreateBySourceRegex(args.id, lldb.SBFileSpec()) 1327330f729Sjoerg bp.AddName("clang::Diagnostic") 1337330f729Sjoerg else: 1347330f729Sjoerg diagtool = getDiagtool(target) 1357330f729Sjoerg list = subprocess.check_output([diagtool, "list-warnings"]).rstrip(); 1367330f729Sjoerg for line in list.splitlines(True): 1377330f729Sjoerg m = re.search(r' *(.*) .*\[\-W' + re.escape(args.id) + r'.*].*', line) 1387330f729Sjoerg # Make sure we only consider warnings. 1397330f729Sjoerg if m and m.group(1).startswith('warn_'): 1407330f729Sjoerg bp = target.BreakpointCreateBySourceRegex(m.group(1), lldb.SBFileSpec()) 1417330f729Sjoerg bp.AddName("clang::Diagnostic") 1427330f729Sjoerg else: 1437330f729Sjoerg print('Adding callbacks.') 1447330f729Sjoerg bp = target.BreakpointCreateByName('DiagnosticsEngine::Report') 1457330f729Sjoerg bp.SetScriptCallbackFunction('clangdiag.setDiagBreakpoint') 1467330f729Sjoerg bp.AddName("clang::Diagnostic") 1477330f729Sjoerg 1487330f729Sjoerg count = target.GetNumBreakpoints() - numOfBreakpoints 1497330f729Sjoerg print('%i breakpoint%s added.' % (count, "s"[count==1:])) 1507330f729Sjoerg 1517330f729Sjoerg return 1527330f729Sjoerg 1537330f729Sjoergdef disable(exe_ctx): 1547330f729Sjoerg target = exe_ctx.GetTarget() 1557330f729Sjoerg # Remove all diag breakpoints. 1567330f729Sjoerg bkpts = lldb.SBBreakpointList(target) 1577330f729Sjoerg target.FindBreakpointsByName("clang::Diagnostic", bkpts) 1587330f729Sjoerg for i in range(bkpts.GetSize()): 1597330f729Sjoerg target.BreakpointDelete(bkpts.GetBreakpointAtIndex(i).GetID()) 1607330f729Sjoerg 1617330f729Sjoerg return 1627330f729Sjoerg 1637330f729Sjoergdef the_diag_command(debugger, command, exe_ctx, result, dict): 1647330f729Sjoerg # Use the Shell Lexer to properly parse up command options just like a 1657330f729Sjoerg # shell would 1667330f729Sjoerg command_args = shlex.split(command) 1677330f729Sjoerg parser = create_diag_options() 1687330f729Sjoerg try: 1697330f729Sjoerg args = parser.parse_args(command_args) 1707330f729Sjoerg except: 1717330f729Sjoerg return 1727330f729Sjoerg 1737330f729Sjoerg if args.subcommands == 'enable': 1747330f729Sjoerg enable(exe_ctx, args) 1757330f729Sjoerg elif args.subcommands == 'disable': 1767330f729Sjoerg disable(exe_ctx) 1777330f729Sjoerg else: 1787330f729Sjoerg diagtool = getDiagtool(exe_ctx.GetTarget(), args.path) 1797330f729Sjoerg print('diagtool = %s' % diagtool) 1807330f729Sjoerg 1817330f729Sjoerg return 1827330f729Sjoerg 1837330f729Sjoergdef __lldb_init_module(debugger, dict): 1847330f729Sjoerg # This initializer is being run from LLDB in the embedded command interpreter 1857330f729Sjoerg # Make the options so we can generate the help text for the new LLDB 1867330f729Sjoerg # command line command prior to registering it with LLDB below 1877330f729Sjoerg parser = create_diag_options() 1887330f729Sjoerg the_diag_command.__doc__ = parser.format_help() 1897330f729Sjoerg # Add any commands contained in this module to LLDB 1907330f729Sjoerg debugger.HandleCommand( 1917330f729Sjoerg 'command script add -f clangdiag.the_diag_command clangdiag') 1927330f729Sjoerg print('The "clangdiag" command has been installed, type "help clangdiag" or "clangdiag --help" for detailed help.') 193