reference, declarationdefinition
definition → references, declarations, derived classes, virtual overrides
reference to multiple definitions → definitions
unreferenced
    1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
#!/usr/bin/python

#----------------------------------------------------------------------
# Be sure to add the python path that points to the LLDB shared library.
#
# # To use this in the embedded python interpreter using "lldb" just
# import it with the full path using the "command script import"
# command
#   (lldb) command script import /path/to/clandiag.py
#----------------------------------------------------------------------

from __future__ import absolute_import, division, print_function
import lldb
import argparse
import shlex
import os
import re
import subprocess

class MyParser(argparse.ArgumentParser):
    def format_help(self):
        return '''     Commands for managing clang diagnostic breakpoints

Syntax: clangdiag enable [<warning>|<diag-name>]
        clangdiag disable
        clangdiag diagtool [<path>|reset]

The following subcommands are supported:

      enable   -- Enable clang diagnostic breakpoints.
      disable  -- Disable all clang diagnostic breakpoints.
      diagtool -- Return, set, or reset diagtool path.

This command sets breakpoints in clang, and clang based tools, that
emit diagnostics.  When a diagnostic is emitted, and clangdiag is
enabled, it will use the appropriate diagtool application to determine
the name of the DiagID, and set breakpoints in all locations that
'diag::name' appears in the source.  Since the new breakpoints are set
after they are encountered, users will need to launch the executable a
second time in order to hit the new breakpoints.

For in-tree builds, the diagtool application, used to map DiagID's to
names, is found automatically in the same directory as the target
executable.  However, out-or-tree builds must use the 'diagtool'
subcommand to set the appropriate path for diagtool in the clang debug
bin directory.  Since this mapping is created at build-time, it's
important for users to use the same version that was generated when
clang was compiled, or else the id's won't match.

Notes:
- Substrings can be passed for both <warning> and <diag-name>.
- If <warning> is passed, only enable the DiagID(s) for that warning.
- If <diag-name> is passed, only enable that DiagID.
- Rerunning enable clears existing breakpoints.
- diagtool is used in breakpoint callbacks, so it can be changed
  without the need to rerun enable.
- Adding this to your ~.lldbinit file makes clangdiag available at startup:
  "command script import /path/to/clangdiag.py"

'''

def create_diag_options():
    parser = MyParser(prog='clangdiag')
    subparsers = parser.add_subparsers(
        title='subcommands',
        dest='subcommands',
        metavar='')
    disable_parser = subparsers.add_parser('disable')
    enable_parser = subparsers.add_parser('enable')
    enable_parser.add_argument('id', nargs='?')
    diagtool_parser = subparsers.add_parser('diagtool')
    diagtool_parser.add_argument('path', nargs='?')
    return parser

def getDiagtool(target, diagtool = None):
    id = target.GetProcess().GetProcessID()
    if 'diagtool' not in getDiagtool.__dict__:
        getDiagtool.diagtool = {}
    if diagtool:
        if diagtool == 'reset':
            getDiagtool.diagtool[id] = None
        elif os.path.exists(diagtool):
            getDiagtool.diagtool[id] = diagtool
        else:
            print('clangdiag: %s not found.' % diagtool)
    if not id in getDiagtool.diagtool or not getDiagtool.diagtool[id]:
        getDiagtool.diagtool[id] = None
        exe = target.GetExecutable()
        if not exe.Exists():
            print('clangdiag: Target (%s) not set.' % exe.GetFilename())
        else:
            diagtool = os.path.join(exe.GetDirectory(), 'diagtool')
            if os.path.exists(diagtool):
                getDiagtool.diagtool[id] = diagtool
            else:
                print('clangdiag: diagtool not found along side %s' % exe)

    return getDiagtool.diagtool[id]

def setDiagBreakpoint(frame, bp_loc, dict):
    id = frame.FindVariable("DiagID").GetValue()
    if id is None:
        print('clangdiag: id is None')
        return False

    # Don't need to test this time, since we did that in enable.
    target = frame.GetThread().GetProcess().GetTarget()
    diagtool = getDiagtool(target)
    name = subprocess.check_output([diagtool, "find-diagnostic-id", id]).rstrip();
    # Make sure we only consider errors, warnings, and extensions.
    # FIXME: Make this configurable?
    prefixes = ['err_', 'warn_', 'exp_']
    if len([prefix for prefix in prefixes+[''] if name.startswith(prefix)][0]):
        bp = target.BreakpointCreateBySourceRegex(name, lldb.SBFileSpec())
        bp.AddName("clang::Diagnostic")

    return False

def enable(exe_ctx, args):
    # Always disable existing breakpoints
    disable(exe_ctx)

    target = exe_ctx.GetTarget()
    numOfBreakpoints = target.GetNumBreakpoints()

    if args.id:
        # Make sure we only consider errors, warnings, and extensions.
        # FIXME: Make this configurable?
        prefixes = ['err_', 'warn_', 'exp_']
        if len([prefix for prefix in prefixes+[''] if args.id.startswith(prefix)][0]):
            bp = target.BreakpointCreateBySourceRegex(args.id, lldb.SBFileSpec())
            bp.AddName("clang::Diagnostic")
        else:
            diagtool = getDiagtool(target)
            list = subprocess.check_output([diagtool, "list-warnings"]).rstrip();
            for line in list.splitlines(True):
                m = re.search(r' *(.*) .*\[\-W' + re.escape(args.id) + r'.*].*', line)
                # Make sure we only consider warnings.
                if m and m.group(1).startswith('warn_'):
                    bp = target.BreakpointCreateBySourceRegex(m.group(1), lldb.SBFileSpec())
                    bp.AddName("clang::Diagnostic")
    else:
        print('Adding callbacks.')
        bp = target.BreakpointCreateByName('DiagnosticsEngine::Report')
        bp.SetScriptCallbackFunction('clangdiag.setDiagBreakpoint')
        bp.AddName("clang::Diagnostic")

    count = target.GetNumBreakpoints() - numOfBreakpoints
    print('%i breakpoint%s added.' % (count, "s"[count==1:]))

    return

def disable(exe_ctx):
    target = exe_ctx.GetTarget()
    # Remove all diag breakpoints.
    bkpts = lldb.SBBreakpointList(target)
    target.FindBreakpointsByName("clang::Diagnostic", bkpts)
    for i in range(bkpts.GetSize()):
        target.BreakpointDelete(bkpts.GetBreakpointAtIndex(i).GetID())

    return

def the_diag_command(debugger, command, exe_ctx, result, dict):
    # Use the Shell Lexer to properly parse up command options just like a
    # shell would
    command_args = shlex.split(command)
    parser = create_diag_options()
    try:
        args = parser.parse_args(command_args)
    except:
        return

    if args.subcommands == 'enable':
        enable(exe_ctx, args)
    elif args.subcommands == 'disable':
        disable(exe_ctx)
    else:
        diagtool = getDiagtool(exe_ctx.GetTarget(), args.path)
        print('diagtool = %s' % diagtool)

    return

def __lldb_init_module(debugger, dict):
    # This initializer is being run from LLDB in the embedded command interpreter
    # Make the options so we can generate the help text for the new LLDB
    # command line command prior to registering it with LLDB below
    parser = create_diag_options()
    the_diag_command.__doc__ = parser.format_help()
    # Add any commands contained in this module to LLDB
    debugger.HandleCommand(
        'command script add -f clangdiag.the_diag_command clangdiag')
    print('The "clangdiag" command has been installed, type "help clangdiag" or "clangdiag --help" for detailed help.')