GDB (API)
|
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()