diff options
Diffstat (limited to 'gdb/contrib/excheck.py')
-rw-r--r-- | gdb/contrib/excheck.py | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/gdb/contrib/excheck.py b/gdb/contrib/excheck.py new file mode 100644 index 0000000..f8f917f --- /dev/null +++ b/gdb/contrib/excheck.py @@ -0,0 +1,296 @@ +# Copyright 2011, 2013 Free Software Foundation, Inc. +# +# This is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. + +# This is a GCC plugin that computes some exception-handling data for +# gdb. This data can then be summarized and checked by the +# exsummary.py script. + +# To use: +# * First, install the GCC Python plugin. See +# https://fedorahosted.org/gcc-python-plugin/ +# * export PYTHON_PLUGIN=/full/path/to/plugin/directory +# This should be the directory holding "python.so". +# * cd build/gdb; make mostlyclean +# * make CC=.../gcc-with-excheck +# This will write a number of .py files in the build directory. +# * python .../exsummary.py +# This will show the violations. + +import gcc +import gccutils +import sys + +# Where our output goes. +output_file = None + +# Cleanup functions require special treatment, because they take a +# function argument, but in theory the function must be nothrow. +cleanup_functions = { + 'make_cleanup': 1, + 'make_cleanup_dtor': 1, + 'make_final_cleanup': 1, + 'make_my_cleanup2': 1, + 'make_my_cleanup': 1 +} + +# Functions which may throw but which we want to ignore. +ignore_functions = { + # This one is super special. + 'exceptions_state_mc': 1, + # gdb generally pretends that internal_error cannot throw, even + # though it can. + 'internal_error': 1, + # do_cleanups and friends are supposedly nothrow but we don't want + # to run afoul of the indirect function call logic. + 'do_cleanups': 1, + 'do_final_cleanups': 1 +} + +# Functions which take a function argument, but which are not +# interesting, usually because the argument is not called in the +# current context. +non_passthrough_functions = { + 'signal': 1, + 'add_internal_function': 1 +} + +# Return True if the type is from Python. +def type_is_pythonic(t): + if isinstance(t, gcc.ArrayType): + t = t.type + if not isinstance(t, gcc.RecordType): + return False + # Hack. + return str(t).find('struct Py') == 0 + +# Examine all the fields of a struct. We don't currently need any +# sort of recursion, so this is simple for now. +def examine_struct_fields(initializer): + global output_file + for idx2, value2 in initializer.elements: + if isinstance(idx2, gcc.Declaration): + if isinstance(value2, gcc.AddrExpr): + value2 = value2.operand + if isinstance(value2, gcc.FunctionDecl): + output_file.write("declare_nothrow(%s)\n" + % repr(str(value2.name))) + +# Examine all global variables looking for pointers to functions in +# structures whose types were defined by Python. +def examine_globals(): + global output_file + vars = gcc.get_variables() + for var in vars: + if not isinstance(var.decl, gcc.VarDecl): + continue + output_file.write("################\n") + output_file.write("# Analysis for %s\n" % var.decl.name) + if not var.decl.initial: + continue + if not type_is_pythonic(var.decl.type): + continue + + if isinstance(var.decl.type, gcc.ArrayType): + for idx, value in var.decl.initial.elements: + examine_struct_fields(value) + else: + gccutils.check_isinstance(var.decl.type, gcc.RecordType) + examine_struct_fields(var.decl.initial) + +# Called at the end of compilation to write out some data derived from +# globals and to close the output. +def close_output(*args): + global output_file + examine_globals() + output_file.close() + +# The pass which derives some exception-checking information. We take +# a two-step approach: first we get a call graph from the compiler. +# This is emitted by the plugin as Python code. Then, we run a second +# program that reads all the generated Python and uses it to get a +# global view of exception routes in gdb. +class GdbExceptionChecker(gcc.GimplePass): + def __init__(self, output_file): + gcc.GimplePass.__init__(self, 'gdb_exception_checker') + self.output_file = output_file + + def log(self, obj): + self.output_file.write("# %s\n" % str(obj)) + + # Return true if FN is a call to a method on a Python object. + # We know these cannot throw in the gdb sense. + def fn_is_python_ignorable(self, fn): + if not isinstance(fn, gcc.SsaName): + return False + stmt = fn.def_stmt + if not isinstance(stmt, gcc.GimpleAssign): + return False + if stmt.exprcode is not gcc.ComponentRef: + return False + rhs = stmt.rhs[0] + if not isinstance(rhs, gcc.ComponentRef): + return False + if not isinstance(rhs.field, gcc.FieldDecl): + return False + return rhs.field.name == 'tp_dealloc' or rhs.field.name == 'tp_free' + + # Decode a function call and write something to the output. + # THIS_FUN is the enclosing function that we are processing. + # FNDECL is the call to process; it might not actually be a DECL + # node. + # LOC is the location of the call. + def handle_one_fndecl(self, this_fun, fndecl, loc): + callee_name = '' + if isinstance(fndecl, gcc.AddrExpr): + fndecl = fndecl.operand + if isinstance(fndecl, gcc.FunctionDecl): + # Ordinary call to a named function. + callee_name = str(fndecl.name) + self.output_file.write("function_call(%s, %s, %s)\n" + % (repr(callee_name), + repr(this_fun.decl.name), + repr(str(loc)))) + elif self.fn_is_python_ignorable(fndecl): + # Call to tp_dealloc. + pass + elif (isinstance(fndecl, gcc.SsaName) + and isinstance(fndecl.var, gcc.ParmDecl)): + # We can ignore an indirect call via a parameter to the + # current function, because this is handled via the rule + # for passthrough functions. + pass + else: + # Any other indirect call. + self.output_file.write("has_indirect_call(%s, %s)\n" + % (repr(this_fun.decl.name), + repr(str(loc)))) + return callee_name + + # This does most of the work for examine_one_bb. + # THIS_FUN is the enclosing function. + # BB is the basic block to process. + # Returns True if this block is the header of a TRY_CATCH, False + # otherwise. + def examine_one_bb_inner(self, this_fun, bb): + if not bb.gimple: + return False + try_catch = False + for stmt in bb.gimple: + loc = stmt.loc + if not loc: + loc = this_fun.decl.location + if not isinstance(stmt, gcc.GimpleCall): + continue + callee_name = self.handle_one_fndecl(this_fun, stmt.fn, loc) + + if callee_name == 'exceptions_state_mc_action_iter': + try_catch = True + + global non_passthrough_functions + if callee_name in non_passthrough_functions: + continue + + # We have to specially handle calls where an argument to + # the call is itself a function, e.g., qsort. In general + # we model these as "passthrough" -- we assume that in + # addition to the call the qsort there is also a call to + # the argument function. + for arg in stmt.args: + # We are only interested in arguments which are functions. + t = arg.type + if isinstance(t, gcc.PointerType): + t = t.dereference + if not isinstance(t, gcc.FunctionType): + continue + + if isinstance(arg, gcc.AddrExpr): + arg = arg.operand + + global cleanup_functions + if callee_name in cleanup_functions: + if not isinstance(arg, gcc.FunctionDecl): + gcc.inform(loc, 'cleanup argument not a DECL: %s' % repr(arg)) + else: + # Cleanups must be nothrow. + self.output_file.write("declare_cleanup(%s)\n" + % repr(str(arg.name))) + else: + # Assume we have a passthrough function, like + # qsort or an iterator. We model this by + # pretending there is an ordinary call at this + # point. + self.handle_one_fndecl(this_fun, arg, loc) + return try_catch + + # Examine all the calls in a basic block and generate output for + # them. + # THIS_FUN is the enclosing function. + # BB is the basic block to examine. + # BB_WORKLIST is a list of basic blocks to work on; we add the + # appropriate successor blocks to this. + # SEEN_BBS is a map whose keys are basic blocks we have already + # processed. We use this to ensure that we only visit a given + # block once. + def examine_one_bb(self, this_fun, bb, bb_worklist, seen_bbs): + try_catch = self.examine_one_bb_inner(this_fun, bb) + for edge in bb.succs: + if edge.dest in seen_bbs: + continue + seen_bbs[edge.dest] = 1 + if try_catch: + # This is bogus, but we magically know the right + # answer. + if edge.false_value: + bb_worklist.append(edge.dest) + else: + bb_worklist.append(edge.dest) + + # Iterate over all basic blocks in THIS_FUN. + def iterate_bbs(self, this_fun): + # Iteration must be in control-flow order, because if we see a + # TRY_CATCH construct we need to drop all the contained blocks. + bb_worklist = [this_fun.cfg.entry] + seen_bbs = {} + seen_bbs[this_fun.cfg.entry] = 1 + for bb in bb_worklist: + self.examine_one_bb(this_fun, bb, bb_worklist, seen_bbs) + + def execute(self, fun): + if fun and fun.cfg and fun.decl: + self.output_file.write("################\n") + self.output_file.write("# Analysis for %s\n" % fun.decl.name) + self.output_file.write("define_function(%s, %s)\n" + % (repr(fun.decl.name), + repr(str(fun.decl.location)))) + + global ignore_functions + if fun.decl.name not in ignore_functions: + self.iterate_bbs(fun) + +def main(**kwargs): + global output_file + output_file = open(gcc.get_dump_base_name() + '.gdb_exc.py', 'w') + # We used to use attributes here, but there didn't seem to be a + # big benefit over hard-coding. + output_file.write('declare_throw("throw_exception")\n') + output_file.write('declare_throw("throw_verror")\n') + output_file.write('declare_throw("throw_vfatal")\n') + output_file.write('declare_throw("throw_error")\n') + gcc.register_callback(gcc.PLUGIN_FINISH_UNIT, close_output) + ps = GdbExceptionChecker(output_file) + ps.register_after('ssa') + +main() |