blob: 6baf65a8761c36151341bf828f9a25ffd7b3a393 [file] [log] [blame]
Don Hintonfc2ffbe2017-10-27 17:02:33 +00001#!/usr/bin/python
2
3#----------------------------------------------------------------------
4# Be sure to add the python path that points to the LLDB shared library.
5#
6# # To use this in the embedded python interpreter using "lldb" just
7# import it with the full path using the "command script import"
8# command
9# (lldb) command script import /path/to/clandiag.py
10#----------------------------------------------------------------------
11
Serge Gueltonb748c0e2018-12-18 16:07:37 +000012from __future__ import absolute_import, division, print_function
Don Hintonfc2ffbe2017-10-27 17:02:33 +000013import lldb
14import argparse
Don Hintonfc2ffbe2017-10-27 17:02:33 +000015import shlex
16import os
17import re
18import subprocess
19
20class MyParser(argparse.ArgumentParser):
21 def format_help(self):
22 return ''' Commands for managing clang diagnostic breakpoints
23
24Syntax: clangdiag enable [<warning>|<diag-name>]
25 clangdiag disable
26 clangdiag diagtool [<path>|reset]
27
28The following subcommands are supported:
29
30 enable -- Enable clang diagnostic breakpoints.
31 disable -- Disable all clang diagnostic breakpoints.
32 diagtool -- Return, set, or reset diagtool path.
33
34This command sets breakpoints in clang, and clang based tools, that
35emit diagnostics. When a diagnostic is emitted, and clangdiag is
36enabled, it will use the appropriate diagtool application to determine
37the name of the DiagID, and set breakpoints in all locations that
38'diag::name' appears in the source. Since the new breakpoints are set
39after they are encountered, users will need to launch the executable a
40second time in order to hit the new breakpoints.
41
42For in-tree builds, the diagtool application, used to map DiagID's to
43names, is found automatically in the same directory as the target
44executable. However, out-or-tree builds must use the 'diagtool'
45subcommand to set the appropriate path for diagtool in the clang debug
46bin directory. Since this mapping is created at build-time, it's
47important for users to use the same version that was generated when
48clang was compiled, or else the id's won't match.
49
50Notes:
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"
59
60'''
61
62def create_diag_options():
63 parser = MyParser(prog='clangdiag')
64 subparsers = parser.add_subparsers(
65 title='subcommands',
66 dest='subcommands',
67 metavar='')
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='?')
73 return parser
74
75def getDiagtool(target, diagtool = None):
76 id = target.GetProcess().GetProcessID()
77 if 'diagtool' not in getDiagtool.__dict__:
78 getDiagtool.diagtool = {}
79 if diagtool:
80 if diagtool == 'reset':
81 getDiagtool.diagtool[id] = None
82 elif os.path.exists(diagtool):
83 getDiagtool.diagtool[id] = diagtool
84 else:
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()
89 if not exe.Exists():
90 print('clangdiag: Target (%s) not set.' % exe.GetFilename())
91 else:
92 diagtool = os.path.join(exe.GetDirectory(), 'diagtool')
93 if os.path.exists(diagtool):
94 getDiagtool.diagtool[id] = diagtool
95 else:
96 print('clangdiag: diagtool not found along side %s' % exe)
97
98 return getDiagtool.diagtool[id]
99
100def setDiagBreakpoint(frame, bp_loc, dict):
101 id = frame.FindVariable("DiagID").GetValue()
102 if id is None:
103 print('clangdiag: id is None')
104 return False
105
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();
Alexander Kornienko2a8c18d2018-04-06 15:14:32 +0000110 # Make sure we only consider errors, warnings, and extensions.
Don Hintonfc2ffbe2017-10-27 17:02:33 +0000111 # 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")
116
117 return False
118
119def enable(exe_ctx, args):
120 # Always disable existing breakpoints
121 disable(exe_ctx)
122
123 target = exe_ctx.GetTarget()
124 numOfBreakpoints = target.GetNumBreakpoints()
125
126 if args.id:
Alexander Kornienko2a8c18d2018-04-06 15:14:32 +0000127 # Make sure we only consider errors, warnings, and extensions.
Don Hintonfc2ffbe2017-10-27 17:02:33 +0000128 # 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")
133 else:
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")
142 else:
143 print('Adding callbacks.')
144 bp = target.BreakpointCreateByName('DiagnosticsEngine::Report')
145 bp.SetScriptCallbackFunction('clangdiag.setDiagBreakpoint')
146 bp.AddName("clang::Diagnostic")
147
148 count = target.GetNumBreakpoints() - numOfBreakpoints
149 print('%i breakpoint%s added.' % (count, "s"[count==1:]))
150
151 return
152
153def 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())
160
161 return
162
163def the_diag_command(debugger, command, exe_ctx, result, dict):
164 # Use the Shell Lexer to properly parse up command options just like a
165 # shell would
166 command_args = shlex.split(command)
167 parser = create_diag_options()
168 try:
169 args = parser.parse_args(command_args)
170 except:
171 return
172
173 if args.subcommands == 'enable':
174 enable(exe_ctx, args)
175 elif args.subcommands == 'disable':
176 disable(exe_ctx)
177 else:
178 diagtool = getDiagtool(exe_ctx.GetTarget(), args.path)
179 print('diagtool = %s' % diagtool)
180
181 return
182
183def __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')
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000192 print('The "clangdiag" command has been installed, type "help clangdiag" or "clangdiag --help" for detailed help.')