GDB (API)
/home/stan/gdb/src/gdb/contrib/excheck.py
Go to the documentation of this file.
00001 #   Copyright 2011, 2013 Free Software Foundation, Inc.
00002 #
00003 #   This is free software: you can redistribute it and/or modify it
00004 #   under the terms of the GNU General Public License as published by
00005 #   the Free Software Foundation, either version 3 of the License, or
00006 #   (at your option) any later version.
00007 #
00008 #   This program is distributed in the hope that it will be useful, but
00009 #   WITHOUT ANY WARRANTY; without even the implied warranty of
00010 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011 #   General Public License for more details.
00012 #
00013 #   You should have received a copy of the GNU General Public License
00014 #   along with this program.  If not, see
00015 #   <http://www.gnu.org/licenses/>.
00016 
00017 # This is a GCC plugin that computes some exception-handling data for
00018 # gdb.  This data can then be summarized and checked by the
00019 # exsummary.py script.
00020 
00021 # To use:
00022 # * First, install the GCC Python plugin.  See
00023 #   https://fedorahosted.org/gcc-python-plugin/
00024 # * export PYTHON_PLUGIN=/full/path/to/plugin/directory
00025 #   This should be the directory holding "python.so".
00026 # * cd build/gdb; make mostlyclean
00027 # * make CC=.../gcc-with-excheck
00028 #   This will write a number of .py files in the build directory.
00029 # * python .../exsummary.py
00030 #   This will show the violations.
00031 
00032 import gcc
00033 import gccutils
00034 import sys
00035 
00036 # Where our output goes.
00037 output_file = None
00038 
00039 # Cleanup functions require special treatment, because they take a
00040 # function argument, but in theory the function must be nothrow.
00041 cleanup_functions = {
00042     'make_cleanup': 1,
00043     'make_cleanup_dtor': 1,
00044     'make_final_cleanup': 1,
00045     'make_my_cleanup2': 1,
00046     'make_my_cleanup': 1
00047 }
00048 
00049 # Functions which may throw but which we want to ignore.
00050 ignore_functions = {
00051     # This one is super special.
00052     'exceptions_state_mc': 1,
00053     # gdb generally pretends that internal_error cannot throw, even
00054     # though it can.
00055     'internal_error': 1,
00056     # do_cleanups and friends are supposedly nothrow but we don't want
00057     # to run afoul of the indirect function call logic.
00058     'do_cleanups': 1,
00059     'do_final_cleanups': 1
00060 }
00061 
00062 # Functions which take a function argument, but which are not
00063 # interesting, usually because the argument is not called in the
00064 # current context.
00065 non_passthrough_functions = {
00066     'signal': 1,
00067     'add_internal_function': 1
00068 }
00069 
00070 # Return True if the type is from Python.
00071 def type_is_pythonic(t):
00072     if isinstance(t, gcc.ArrayType):
00073         t = t.type
00074     if not isinstance(t, gcc.RecordType):
00075         return False
00076     # Hack.
00077     return str(t).find('struct Py') == 0
00078 
00079 # Examine all the fields of a struct.  We don't currently need any
00080 # sort of recursion, so this is simple for now.
00081 def examine_struct_fields(initializer):
00082     global output_file
00083     for idx2, value2 in initializer.elements:
00084         if isinstance(idx2, gcc.Declaration):
00085             if isinstance(value2, gcc.AddrExpr):
00086                 value2 = value2.operand
00087                 if isinstance(value2, gcc.FunctionDecl):
00088                     output_file.write("declare_nothrow(%s)\n"
00089                                       % repr(str(value2.name)))
00090 
00091 # Examine all global variables looking for pointers to functions in
00092 # structures whose types were defined by Python.
00093 def examine_globals():
00094     global output_file
00095     vars = gcc.get_variables()
00096     for var in vars:
00097         if not isinstance(var.decl, gcc.VarDecl):
00098             continue
00099         output_file.write("################\n")
00100         output_file.write("# Analysis for %s\n" % var.decl.name)
00101         if not var.decl.initial:
00102             continue
00103         if not type_is_pythonic(var.decl.type):
00104             continue
00105 
00106         if isinstance(var.decl.type, gcc.ArrayType):
00107             for idx, value in var.decl.initial.elements:
00108                 examine_struct_fields(value)
00109         else:
00110             gccutils.check_isinstance(var.decl.type, gcc.RecordType)
00111             examine_struct_fields(var.decl.initial)
00112 
00113 # Called at the end of compilation to write out some data derived from
00114 # globals and to close the output.
00115 def close_output(*args):
00116     global output_file
00117     examine_globals()
00118     output_file.close()
00119 
00120 # The pass which derives some exception-checking information.  We take
00121 # a two-step approach: first we get a call graph from the compiler.
00122 # This is emitted by the plugin as Python code.  Then, we run a second
00123 # program that reads all the generated Python and uses it to get a
00124 # global view of exception routes in gdb.
00125 class GdbExceptionChecker(gcc.GimplePass):
00126     def __init__(self, output_file):
00127         gcc.GimplePass.__init__(self, 'gdb_exception_checker')
00128         self.output_file = output_file
00129 
00130     def log(self, obj):
00131         self.output_file.write("# %s\n" % str(obj))
00132 
00133     # Return true if FN is a call to a method on a Python object.
00134     # We know these cannot throw in the gdb sense.
00135     def fn_is_python_ignorable(self, fn):
00136         if not isinstance(fn, gcc.SsaName):
00137             return False
00138         stmt = fn.def_stmt
00139         if not isinstance(stmt, gcc.GimpleAssign):
00140             return False
00141         if stmt.exprcode is not gcc.ComponentRef:
00142             return False
00143         rhs = stmt.rhs[0]
00144         if not isinstance(rhs, gcc.ComponentRef):
00145             return False
00146         if not isinstance(rhs.field, gcc.FieldDecl):
00147             return False
00148         return rhs.field.name == 'tp_dealloc' or rhs.field.name == 'tp_free'
00149 
00150     # Decode a function call and write something to the output.
00151     # THIS_FUN is the enclosing function that we are processing.
00152     # FNDECL is the call to process; it might not actually be a DECL
00153     # node.
00154     # LOC is the location of the call.
00155     def handle_one_fndecl(self, this_fun, fndecl, loc):
00156         callee_name = ''
00157         if isinstance(fndecl, gcc.AddrExpr):
00158             fndecl = fndecl.operand
00159         if isinstance(fndecl, gcc.FunctionDecl):
00160             # Ordinary call to a named function.
00161             callee_name = str(fndecl.name)
00162             self.output_file.write("function_call(%s, %s, %s)\n"
00163                                    % (repr(callee_name),
00164                                       repr(this_fun.decl.name),
00165                                       repr(str(loc))))
00166         elif self.fn_is_python_ignorable(fndecl):
00167             # Call to tp_dealloc.
00168             pass
00169         elif (isinstance(fndecl, gcc.SsaName)
00170               and isinstance(fndecl.var, gcc.ParmDecl)):
00171             # We can ignore an indirect call via a parameter to the
00172             # current function, because this is handled via the rule
00173             # for passthrough functions.
00174             pass
00175         else:
00176             # Any other indirect call.
00177             self.output_file.write("has_indirect_call(%s, %s)\n"
00178                                    % (repr(this_fun.decl.name),
00179                                       repr(str(loc))))
00180         return callee_name
00181 
00182     # This does most of the work for examine_one_bb.
00183     # THIS_FUN is the enclosing function.
00184     # BB is the basic block to process.
00185     # Returns True if this block is the header of a TRY_CATCH, False
00186     # otherwise.
00187     def examine_one_bb_inner(self, this_fun, bb):
00188         if not bb.gimple:
00189             return False
00190         try_catch = False
00191         for stmt in bb.gimple:
00192             loc = stmt.loc
00193             if not loc:
00194                 loc = this_fun.decl.location
00195             if not isinstance(stmt, gcc.GimpleCall):
00196                 continue
00197             callee_name = self.handle_one_fndecl(this_fun, stmt.fn, loc)
00198 
00199             if callee_name == 'exceptions_state_mc_action_iter':
00200                 try_catch = True
00201 
00202             global non_passthrough_functions
00203             if callee_name in non_passthrough_functions:
00204                 continue
00205 
00206             # We have to specially handle calls where an argument to
00207             # the call is itself a function, e.g., qsort.  In general
00208             # we model these as "passthrough" -- we assume that in
00209             # addition to the call the qsort there is also a call to
00210             # the argument function.
00211             for arg in stmt.args:
00212                 # We are only interested in arguments which are functions.
00213                 t = arg.type
00214                 if isinstance(t, gcc.PointerType):
00215                     t = t.dereference
00216                 if not isinstance(t, gcc.FunctionType):
00217                     continue
00218 
00219                 if isinstance(arg, gcc.AddrExpr):
00220                     arg = arg.operand
00221 
00222                 global cleanup_functions
00223                 if callee_name in cleanup_functions:
00224                     if not isinstance(arg, gcc.FunctionDecl):
00225                         gcc.inform(loc, 'cleanup argument not a DECL: %s' % repr(arg))
00226                     else:
00227                         # Cleanups must be nothrow.
00228                         self.output_file.write("declare_cleanup(%s)\n"
00229                                                % repr(str(arg.name)))
00230                 else:
00231                     # Assume we have a passthrough function, like
00232                     # qsort or an iterator.  We model this by
00233                     # pretending there is an ordinary call at this
00234                     # point.
00235                     self.handle_one_fndecl(this_fun, arg, loc)
00236         return try_catch
00237 
00238     # Examine all the calls in a basic block and generate output for
00239     # them.
00240     # THIS_FUN is the enclosing function.
00241     # BB is the basic block to examine.
00242     # BB_WORKLIST is a list of basic blocks to work on; we add the
00243     # appropriate successor blocks to this.
00244     # SEEN_BBS is a map whose keys are basic blocks we have already
00245     # processed.  We use this to ensure that we only visit a given
00246     # block once.
00247     def examine_one_bb(self, this_fun, bb, bb_worklist, seen_bbs):
00248         try_catch = self.examine_one_bb_inner(this_fun, bb)
00249         for edge in bb.succs:
00250             if edge.dest in seen_bbs:
00251                 continue
00252             seen_bbs[edge.dest] = 1
00253             if try_catch:
00254                 # This is bogus, but we magically know the right
00255                 # answer.
00256                 if edge.false_value:
00257                     bb_worklist.append(edge.dest)
00258             else:
00259                 bb_worklist.append(edge.dest)
00260 
00261     # Iterate over all basic blocks in THIS_FUN.
00262     def iterate_bbs(self, this_fun):
00263         # Iteration must be in control-flow order, because if we see a
00264         # TRY_CATCH construct we need to drop all the contained blocks.
00265         bb_worklist = [this_fun.cfg.entry]
00266         seen_bbs = {}
00267         seen_bbs[this_fun.cfg.entry] = 1
00268         for bb in bb_worklist:
00269             self.examine_one_bb(this_fun, bb, bb_worklist, seen_bbs)
00270 
00271     def execute(self, fun):
00272         if fun and fun.cfg and fun.decl:
00273             self.output_file.write("################\n")
00274             self.output_file.write("# Analysis for %s\n" % fun.decl.name)
00275             self.output_file.write("define_function(%s, %s)\n"
00276                                    % (repr(fun.decl.name),
00277                                       repr(str(fun.decl.location))))
00278 
00279             global ignore_functions
00280             if fun.decl.name not in ignore_functions:
00281                 self.iterate_bbs(fun)
00282 
00283 def main(**kwargs):
00284     global output_file
00285     output_file = open(gcc.get_dump_base_name() + '.gdb_exc.py', 'w')
00286     # We used to use attributes here, but there didn't seem to be a
00287     # big benefit over hard-coding.
00288     output_file.write('declare_throw("throw_exception")\n')
00289     output_file.write('declare_throw("throw_verror")\n')
00290     output_file.write('declare_throw("throw_vfatal")\n')
00291     output_file.write('declare_throw("throw_error")\n')
00292     gcc.register_callback(gcc.PLUGIN_FINISH_UNIT, close_output)
00293     ps = GdbExceptionChecker(output_file)
00294     ps.register_after('ssa')
00295 
00296 main()
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines