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