gdb/contrib/excheck.py - gdb

Global variables defined

Functions defined

Source code

  1. #   Copyright 2011-2015 Free Software Foundation, Inc.
  2. #
  3. #   This is free software: you can redistribute it and/or modify it
  4. #   under the terms of the GNU General Public License as published by
  5. #   the Free Software Foundation, either version 3 of the License, or
  6. #   (at your option) any later version.
  7. #
  8. #   This program is distributed in the hope that it will be useful, but
  9. #   WITHOUT ANY WARRANTY; without even the implied warranty of
  10. #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  11. #   General Public License for more details.
  12. #
  13. #   You should have received a copy of the GNU General Public License
  14. #   along with this program.  If not, see
  15. #   <http://www.gnu.org/licenses/>.

  16. # This is a GCC plugin that computes some exception-handling data for
  17. # gdb.  This data can then be summarized and checked by the
  18. # exsummary.py script.

  19. # To use:
  20. # * First, install the GCC Python plugin.  See
  21. #   https://fedorahosted.org/gcc-python-plugin/
  22. # * export PYTHON_PLUGIN=/full/path/to/plugin/directory
  23. #   This should be the directory holding "python.so".
  24. # * cd build/gdb; make mostlyclean
  25. # * make CC=.../gcc-with-excheck
  26. #   This will write a number of .py files in the build directory.
  27. # * python .../exsummary.py
  28. #   This will show the violations.

  29. import gcc
  30. import gccutils
  31. import sys

  32. # Where our output goes.
  33. output_file = None

  34. # Cleanup functions require special treatment, because they take a
  35. # function argument, but in theory the function must be nothrow.
  36. cleanup_functions = {
  37.     'make_cleanup': 1,
  38.     'make_cleanup_dtor': 1,
  39.     'make_final_cleanup': 1,
  40.     'make_my_cleanup2': 1,
  41.     'make_my_cleanup': 1
  42. }

  43. # Functions which may throw but which we want to ignore.
  44. ignore_functions = {
  45.     # This one is super special.
  46.     'exceptions_state_mc': 1,
  47.     # gdb generally pretends that internal_error cannot throw, even
  48.     # though it can.
  49.     'internal_error': 1,
  50.     # do_cleanups and friends are supposedly nothrow but we don't want
  51.     # to run afoul of the indirect function call logic.
  52.     'do_cleanups': 1,
  53.     'do_final_cleanups': 1
  54. }

  55. # Functions which take a function argument, but which are not
  56. # interesting, usually because the argument is not called in the
  57. # current context.
  58. non_passthrough_functions = {
  59.     'signal': 1,
  60.     'add_internal_function': 1
  61. }

  62. # Return True if the type is from Python.
  63. def type_is_pythonic(t):
  64.     if isinstance(t, gcc.ArrayType):
  65.         t = t.type
  66.     if not isinstance(t, gcc.RecordType):
  67.         return False
  68.     # Hack.
  69.     return str(t).find('struct Py') == 0

  70. # Examine all the fields of a struct.  We don't currently need any
  71. # sort of recursion, so this is simple for now.
  72. def examine_struct_fields(initializer):
  73.     global output_file
  74.     for idx2, value2 in initializer.elements:
  75.         if isinstance(idx2, gcc.Declaration):
  76.             if isinstance(value2, gcc.AddrExpr):
  77.                 value2 = value2.operand
  78.                 if isinstance(value2, gcc.FunctionDecl):
  79.                     output_file.write("declare_nothrow(%s)\n"
  80.                                       % repr(str(value2.name)))

  81. # Examine all global variables looking for pointers to functions in
  82. # structures whose types were defined by Python.
  83. def examine_globals():
  84.     global output_file
  85.     vars = gcc.get_variables()
  86.     for var in vars:
  87.         if not isinstance(var.decl, gcc.VarDecl):
  88.             continue
  89.         output_file.write("################\n")
  90.         output_file.write("# Analysis for %s\n" % var.decl.name)
  91.         if not var.decl.initial:
  92.             continue
  93.         if not type_is_pythonic(var.decl.type):
  94.             continue

  95.         if isinstance(var.decl.type, gcc.ArrayType):
  96.             for idx, value in var.decl.initial.elements:
  97.                 examine_struct_fields(value)
  98.         else:
  99.             gccutils.check_isinstance(var.decl.type, gcc.RecordType)
  100.             examine_struct_fields(var.decl.initial)

  101. # Called at the end of compilation to write out some data derived from
  102. # globals and to close the output.
  103. def close_output(*args):
  104.     global output_file
  105.     examine_globals()
  106.     output_file.close()

  107. # The pass which derives some exception-checking information.  We take
  108. # a two-step approach: first we get a call graph from the compiler.
  109. # This is emitted by the plugin as Python code.  Then, we run a second
  110. # program that reads all the generated Python and uses it to get a
  111. # global view of exception routes in gdb.
  112. class GdbExceptionChecker(gcc.GimplePass):
  113.     def __init__(self, output_file):
  114.         gcc.GimplePass.__init__(self, 'gdb_exception_checker')
  115.         self.output_file = output_file

  116.     def log(self, obj):
  117.         self.output_file.write("# %s\n" % str(obj))

  118.     # Return true if FN is a call to a method on a Python object.
  119.     # We know these cannot throw in the gdb sense.
  120.     def fn_is_python_ignorable(self, fn):
  121.         if not isinstance(fn, gcc.SsaName):
  122.             return False
  123.         stmt = fn.def_stmt
  124.         if not isinstance(stmt, gcc.GimpleAssign):
  125.             return False
  126.         if stmt.exprcode is not gcc.ComponentRef:
  127.             return False
  128.         rhs = stmt.rhs[0]
  129.         if not isinstance(rhs, gcc.ComponentRef):
  130.             return False
  131.         if not isinstance(rhs.field, gcc.FieldDecl):
  132.             return False
  133.         return rhs.field.name == 'tp_dealloc' or rhs.field.name == 'tp_free'

  134.     # Decode a function call and write something to the output.
  135.     # THIS_FUN is the enclosing function that we are processing.
  136.     # FNDECL is the call to process; it might not actually be a DECL
  137.     # node.
  138.     # LOC is the location of the call.
  139.     def handle_one_fndecl(self, this_fun, fndecl, loc):
  140.         callee_name = ''
  141.         if isinstance(fndecl, gcc.AddrExpr):
  142.             fndecl = fndecl.operand
  143.         if isinstance(fndecl, gcc.FunctionDecl):
  144.             # Ordinary call to a named function.
  145.             callee_name = str(fndecl.name)
  146.             self.output_file.write("function_call(%s, %s, %s)\n"
  147.                                    % (repr(callee_name),
  148.                                       repr(this_fun.decl.name),
  149.                                       repr(str(loc))))
  150.         elif self.fn_is_python_ignorable(fndecl):
  151.             # Call to tp_dealloc.
  152.             pass
  153.         elif (isinstance(fndecl, gcc.SsaName)
  154.               and isinstance(fndecl.var, gcc.ParmDecl)):
  155.             # We can ignore an indirect call via a parameter to the
  156.             # current function, because this is handled via the rule
  157.             # for passthrough functions.
  158.             pass
  159.         else:
  160.             # Any other indirect call.
  161.             self.output_file.write("has_indirect_call(%s, %s)\n"
  162.                                    % (repr(this_fun.decl.name),
  163.                                       repr(str(loc))))
  164.         return callee_name

  165.     # This does most of the work for examine_one_bb.
  166.     # THIS_FUN is the enclosing function.
  167.     # BB is the basic block to process.
  168.     # Returns True if this block is the header of a TRY_CATCH, False
  169.     # otherwise.
  170.     def examine_one_bb_inner(self, this_fun, bb):
  171.         if not bb.gimple:
  172.             return False
  173.         try_catch = False
  174.         for stmt in bb.gimple:
  175.             loc = stmt.loc
  176.             if not loc:
  177.                 loc = this_fun.decl.location
  178.             if not isinstance(stmt, gcc.GimpleCall):
  179.                 continue
  180.             callee_name = self.handle_one_fndecl(this_fun, stmt.fn, loc)

  181.             if callee_name == 'exceptions_state_mc_action_iter':
  182.                 try_catch = True

  183.             global non_passthrough_functions
  184.             if callee_name in non_passthrough_functions:
  185.                 continue

  186.             # We have to specially handle calls where an argument to
  187.             # the call is itself a function, e.g., qsort.  In general
  188.             # we model these as "passthrough" -- we assume that in
  189.             # addition to the call the qsort there is also a call to
  190.             # the argument function.
  191.             for arg in stmt.args:
  192.                 # We are only interested in arguments which are functions.
  193.                 t = arg.type
  194.                 if isinstance(t, gcc.PointerType):
  195.                     t = t.dereference
  196.                 if not isinstance(t, gcc.FunctionType):
  197.                     continue

  198.                 if isinstance(arg, gcc.AddrExpr):
  199.                     arg = arg.operand

  200.                 global cleanup_functions
  201.                 if callee_name in cleanup_functions:
  202.                     if not isinstance(arg, gcc.FunctionDecl):
  203.                         gcc.inform(loc, 'cleanup argument not a DECL: %s' % repr(arg))
  204.                     else:
  205.                         # Cleanups must be nothrow.
  206.                         self.output_file.write("declare_cleanup(%s)\n"
  207.                                                % repr(str(arg.name)))
  208.                 else:
  209.                     # Assume we have a passthrough function, like
  210.                     # qsort or an iterator.  We model this by
  211.                     # pretending there is an ordinary call at this
  212.                     # point.
  213.                     self.handle_one_fndecl(this_fun, arg, loc)
  214.         return try_catch

  215.     # Examine all the calls in a basic block and generate output for
  216.     # them.
  217.     # THIS_FUN is the enclosing function.
  218.     # BB is the basic block to examine.
  219.     # BB_WORKLIST is a list of basic blocks to work on; we add the
  220.     # appropriate successor blocks to this.
  221.     # SEEN_BBS is a map whose keys are basic blocks we have already
  222.     # processed.  We use this to ensure that we only visit a given
  223.     # block once.
  224.     def examine_one_bb(self, this_fun, bb, bb_worklist, seen_bbs):
  225.         try_catch = self.examine_one_bb_inner(this_fun, bb)
  226.         for edge in bb.succs:
  227.             if edge.dest in seen_bbs:
  228.                 continue
  229.             seen_bbs[edge.dest] = 1
  230.             if try_catch:
  231.                 # This is bogus, but we magically know the right
  232.                 # answer.
  233.                 if edge.false_value:
  234.                     bb_worklist.append(edge.dest)
  235.             else:
  236.                 bb_worklist.append(edge.dest)

  237.     # Iterate over all basic blocks in THIS_FUN.
  238.     def iterate_bbs(self, this_fun):
  239.         # Iteration must be in control-flow order, because if we see a
  240.         # TRY_CATCH construct we need to drop all the contained blocks.
  241.         bb_worklist = [this_fun.cfg.entry]
  242.         seen_bbs = {}
  243.         seen_bbs[this_fun.cfg.entry] = 1
  244.         for bb in bb_worklist:
  245.             self.examine_one_bb(this_fun, bb, bb_worklist, seen_bbs)

  246.     def execute(self, fun):
  247.         if fun and fun.cfg and fun.decl:
  248.             self.output_file.write("################\n")
  249.             self.output_file.write("# Analysis for %s\n" % fun.decl.name)
  250.             self.output_file.write("define_function(%s, %s)\n"
  251.                                    % (repr(fun.decl.name),
  252.                                       repr(str(fun.decl.location))))

  253.             global ignore_functions
  254.             if fun.decl.name not in ignore_functions:
  255.                 self.iterate_bbs(fun)

  256. def main(**kwargs):
  257.     global output_file
  258.     output_file = open(gcc.get_dump_base_name() + '.gdb_exc.py', 'w')
  259.     # We used to use attributes here, but there didn't seem to be a
  260.     # big benefit over hard-coding.
  261.     output_file.write('declare_throw("throw_exception")\n')
  262.     output_file.write('declare_throw("throw_verror")\n')
  263.     output_file.write('declare_throw("throw_vfatal")\n')
  264.     output_file.write('declare_throw("throw_error")\n')
  265.     gcc.register_callback(gcc.PLUGIN_FINISH_UNIT, close_output)
  266.     ps = GdbExceptionChecker(output_file)
  267.     ps.register_after('ssa')

  268. main()