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/env 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.')
|