Home | History | Annotate | Line # | Download | only in utils
      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