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
21 class MyParser(argparse
.ArgumentParser
):
22 def format_help(self
):
23 return """ Commands for managing clang diagnostic breakpoints
25 Syntax: clangdiag enable [<warning>|<diag-name>]
27 clangdiag diagtool [<path>|reset]
29 The following subcommands are supported:
31 enable -- Enable clang diagnostic breakpoints.
32 disable -- Disable all clang diagnostic breakpoints.
33 diagtool -- Return, set, or reset diagtool path.
35 This command sets breakpoints in clang, and clang based tools, that
36 emit diagnostics. When a diagnostic is emitted, and clangdiag is
37 enabled, it will use the appropriate diagtool application to determine
38 the name of the DiagID, and set breakpoints in all locations that
39 'diag::name' appears in the source. Since the new breakpoints are set
40 after they are encountered, users will need to launch the executable a
41 second time in order to hit the new breakpoints.
43 For in-tree builds, the diagtool application, used to map DiagID's to
44 names, is found automatically in the same directory as the target
45 executable. However, out-or-tree builds must use the 'diagtool'
46 subcommand to set the appropriate path for diagtool in the clang debug
47 bin directory. Since this mapping is created at build-time, it's
48 important for users to use the same version that was generated when
49 clang was compiled, or else the id's won't match.
52 - Substrings can be passed for both <warning> and <diag-name>.
53 - If <warning> is passed, only enable the DiagID(s) for that warning.
54 - If <diag-name> is passed, only enable that DiagID.
55 - Rerunning enable clears existing breakpoints.
56 - diagtool is used in breakpoint callbacks, so it can be changed
57 without the need to rerun enable.
58 - Adding this to your ~.lldbinit file makes clangdiag available at startup:
59 "command script import /path/to/clangdiag.py"
64 def create_diag_options():
65 parser
= MyParser(prog
="clangdiag")
66 subparsers
= parser
.add_subparsers(
67 title
="subcommands", dest
="subcommands", metavar
=""
69 disable_parser
= subparsers
.add_parser("disable")
70 enable_parser
= subparsers
.add_parser("enable")
71 enable_parser
.add_argument("id", nargs
="?")
72 diagtool_parser
= subparsers
.add_parser("diagtool")
73 diagtool_parser
.add_argument("path", nargs
="?")
77 def getDiagtool(target
, diagtool
=None):
78 id = target
.GetProcess().GetProcessID()
79 if "diagtool" not in getDiagtool
.__dict
__:
80 getDiagtool
.diagtool
= {}
82 if diagtool
== "reset":
83 getDiagtool
.diagtool
[id] = None
84 elif os
.path
.exists(diagtool
):
85 getDiagtool
.diagtool
[id] = diagtool
87 print("clangdiag: %s not found." % diagtool
)
88 if not id in getDiagtool
.diagtool
or not getDiagtool
.diagtool
[id]:
89 getDiagtool
.diagtool
[id] = None
90 exe
= target
.GetExecutable()
92 print("clangdiag: Target (%s) not set." % exe
.GetFilename())
94 diagtool
= os
.path
.join(exe
.GetDirectory(), "diagtool")
95 if os
.path
.exists(diagtool
):
96 getDiagtool
.diagtool
[id] = diagtool
98 print("clangdiag: diagtool not found along side %s" % exe
)
100 return getDiagtool
.diagtool
[id]
103 def setDiagBreakpoint(frame
, bp_loc
, dict):
104 id = frame
.FindVariable("DiagID").GetValue()
106 print("clangdiag: id is None")
109 # Don't need to test this time, since we did that in enable.
110 target
= frame
.GetThread().GetProcess().GetTarget()
111 diagtool
= getDiagtool(target
)
112 name
= subprocess
.check_output([diagtool
, "find-diagnostic-id", id]).rstrip()
113 # Make sure we only consider errors, warnings, and extensions.
114 # FIXME: Make this configurable?
115 prefixes
= ["err_", "warn_", "exp_"]
116 if len([prefix
for prefix
in prefixes
+ [""] if name
.startswith(prefix
)][0]):
117 bp
= target
.BreakpointCreateBySourceRegex(name
, lldb
.SBFileSpec())
118 bp
.AddName("clang::Diagnostic")
123 def enable(exe_ctx
, args
):
124 # Always disable existing breakpoints
127 target
= exe_ctx
.GetTarget()
128 numOfBreakpoints
= target
.GetNumBreakpoints()
131 # Make sure we only consider errors, warnings, and extensions.
132 # FIXME: Make this configurable?
133 prefixes
= ["err_", "warn_", "exp_"]
134 if len([prefix
for prefix
in prefixes
+ [""] if args
.id.startswith(prefix
)][0]):
135 bp
= target
.BreakpointCreateBySourceRegex(args
.id, lldb
.SBFileSpec())
136 bp
.AddName("clang::Diagnostic")
138 diagtool
= getDiagtool(target
)
139 list = subprocess
.check_output([diagtool
, "list-warnings"]).rstrip()
140 for line
in list.splitlines(True):
141 m
= re
.search(r
" *(.*) .*\[\-W" + re
.escape(args
.id) + r
".*].*", line
)
142 # Make sure we only consider warnings.
143 if m
and m
.group(1).startswith("warn_"):
144 bp
= target
.BreakpointCreateBySourceRegex(
145 m
.group(1), lldb
.SBFileSpec()
147 bp
.AddName("clang::Diagnostic")
149 print("Adding callbacks.")
150 bp
= target
.BreakpointCreateByName("DiagnosticsEngine::Report")
151 bp
.SetScriptCallbackFunction("clangdiag.setDiagBreakpoint")
152 bp
.AddName("clang::Diagnostic")
154 count
= target
.GetNumBreakpoints() - numOfBreakpoints
155 print("%i breakpoint%s added." % (count
, "s"[count
== 1 :]))
160 def disable(exe_ctx
):
161 target
= exe_ctx
.GetTarget()
162 # Remove all diag breakpoints.
163 bkpts
= lldb
.SBBreakpointList(target
)
164 target
.FindBreakpointsByName("clang::Diagnostic", bkpts
)
165 for i
in range(bkpts
.GetSize()):
166 target
.BreakpointDelete(bkpts
.GetBreakpointAtIndex(i
).GetID())
171 def the_diag_command(debugger
, command
, exe_ctx
, result
, dict):
172 # Use the Shell Lexer to properly parse up command options just like a
174 command_args
= shlex
.split(command
)
175 parser
= create_diag_options()
177 args
= parser
.parse_args(command_args
)
181 if args
.subcommands
== "enable":
182 enable(exe_ctx
, args
)
183 elif args
.subcommands
== "disable":
186 diagtool
= getDiagtool(exe_ctx
.GetTarget(), args
.path
)
187 print("diagtool = %s" % diagtool
)
192 def __lldb_init_module(debugger
, dict):
193 # This initializer is being run from LLDB in the embedded command interpreter
194 # Make the options so we can generate the help text for the new LLDB
195 # command line command prior to registering it with LLDB below
196 parser
= create_diag_options()
197 the_diag_command
.__doc
__ = parser
.format_help()
198 # Add any commands contained in this module to LLDB
199 debugger
.HandleCommand("command script add -f clangdiag.the_diag_command clangdiag")
201 'The "clangdiag" command has been installed, type "help clangdiag" or "clangdiag --help" for detailed help.'