1 | #!/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 | |
12 | from __future__ import absolute_import, division, print_function |
13 | import lldb |
14 | import argparse |
15 | import shlex |
16 | import os |
17 | import re |
18 | import subprocess |
19 | |
20 | class MyParser(argparse.ArgumentParser): |
21 | def format_help(self): |
22 | return ''' Commands for managing clang diagnostic breakpoints |
23 | |
24 | Syntax: clangdiag enable [<warning>|<diag-name>] |
25 | clangdiag disable |
26 | clangdiag diagtool [<path>|reset] |
27 | |
28 | The 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 | |
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. |
41 | |
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. |
49 | |
50 | Notes: |
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 | |
62 | def 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 | |
75 | def 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 | |
100 | def 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(); |
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") |
116 | |
117 | return False |
118 | |
119 | def 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: |
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") |
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 | |
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()) |
160 | |
161 | return |
162 | |
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 |
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 | |
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.') |
193 | |