123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- #!/usr/bin/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.')
|