clangdiag.py 7.4 KB

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