1# Copyright 2011, 2013 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 17# This is a GCC plugin that computes some exception-handling data for 18# gdb. This data can then be summarized and checked by the 19# exsummary.py script. 20 21# To use: 22# * First, install the GCC Python plugin. See 23# https://fedorahosted.org/gcc-python-plugin/ 24# * export PYTHON_PLUGIN=/full/path/to/plugin/directory 25# This should be the directory holding "python.so". 26# * cd build/gdb; make mostlyclean 27# * make CC=.../gcc-with-excheck 28# This will write a number of .py files in the build directory. 29# * python .../exsummary.py 30# This will show the violations. 31 32import gcc 33import gccutils 34import sys 35 36# Where our output goes. 37output_file = None 38 39# Cleanup functions require special treatment, because they take a 40# function argument, but in theory the function must be nothrow. 41cleanup_functions = { 42 'make_cleanup': 1, 43 'make_cleanup_dtor': 1, 44 'make_final_cleanup': 1, 45 'make_my_cleanup2': 1, 46 'make_my_cleanup': 1 47} 48 49# Functions which may throw but which we want to ignore. 50ignore_functions = { 51 # This one is super special. 52 'exceptions_state_mc': 1, 53 # gdb generally pretends that internal_error cannot throw, even 54 # though it can. 55 'internal_error': 1, 56 # do_cleanups and friends are supposedly nothrow but we don't want 57 # to run afoul of the indirect function call logic. 58 'do_cleanups': 1, 59 'do_final_cleanups': 1 60} 61 62# Functions which take a function argument, but which are not 63# interesting, usually because the argument is not called in the 64# current context. 65non_passthrough_functions = { 66 'signal': 1, 67 'add_internal_function': 1 68} 69 70# Return True if the type is from Python. 71def type_is_pythonic(t): 72 if isinstance(t, gcc.ArrayType): 73 t = t.type 74 if not isinstance(t, gcc.RecordType): 75 return False 76 # Hack. 77 return str(t).find('struct Py') == 0 78 79# Examine all the fields of a struct. We don't currently need any 80# sort of recursion, so this is simple for now. 81def examine_struct_fields(initializer): 82 global output_file 83 for idx2, value2 in initializer.elements: 84 if isinstance(idx2, gcc.Declaration): 85 if isinstance(value2, gcc.AddrExpr): 86 value2 = value2.operand 87 if isinstance(value2, gcc.FunctionDecl): 88 output_file.write("declare_nothrow(%s)\n" 89 % repr(str(value2.name))) 90 91# Examine all global variables looking for pointers to functions in 92# structures whose types were defined by Python. 93def examine_globals(): 94 global output_file 95 vars = gcc.get_variables() 96 for var in vars: 97 if not isinstance(var.decl, gcc.VarDecl): 98 continue 99 output_file.write("################\n") 100 output_file.write("# Analysis for %s\n" % var.decl.name) 101 if not var.decl.initial: 102 continue 103 if not type_is_pythonic(var.decl.type): 104 continue 105 106 if isinstance(var.decl.type, gcc.ArrayType): 107 for idx, value in var.decl.initial.elements: 108 examine_struct_fields(value) 109 else: 110 gccutils.check_isinstance(var.decl.type, gcc.RecordType) 111 examine_struct_fields(var.decl.initial) 112 113# Called at the end of compilation to write out some data derived from 114# globals and to close the output. 115def close_output(*args): 116 global output_file 117 examine_globals() 118 output_file.close() 119 120# The pass which derives some exception-checking information. We take 121# a two-step approach: first we get a call graph from the compiler. 122# This is emitted by the plugin as Python code. Then, we run a second 123# program that reads all the generated Python and uses it to get a 124# global view of exception routes in gdb. 125class GdbExceptionChecker(gcc.GimplePass): 126 def __init__(self, output_file): 127 gcc.GimplePass.__init__(self, 'gdb_exception_checker') 128 self.output_file = output_file 129 130 def log(self, obj): 131 self.output_file.write("# %s\n" % str(obj)) 132 133 # Return true if FN is a call to a method on a Python object. 134 # We know these cannot throw in the gdb sense. 135 def fn_is_python_ignorable(self, fn): 136 if not isinstance(fn, gcc.SsaName): 137 return False 138 stmt = fn.def_stmt 139 if not isinstance(stmt, gcc.GimpleAssign): 140 return False 141 if stmt.exprcode is not gcc.ComponentRef: 142 return False 143 rhs = stmt.rhs[0] 144 if not isinstance(rhs, gcc.ComponentRef): 145 return False 146 if not isinstance(rhs.field, gcc.FieldDecl): 147 return False 148 return rhs.field.name == 'tp_dealloc' or rhs.field.name == 'tp_free' 149 150 # Decode a function call and write something to the output. 151 # THIS_FUN is the enclosing function that we are processing. 152 # FNDECL is the call to process; it might not actually be a DECL 153 # node. 154 # LOC is the location of the call. 155 def handle_one_fndecl(self, this_fun, fndecl, loc): 156 callee_name = '' 157 if isinstance(fndecl, gcc.AddrExpr): 158 fndecl = fndecl.operand 159 if isinstance(fndecl, gcc.FunctionDecl): 160 # Ordinary call to a named function. 161 callee_name = str(fndecl.name) 162 self.output_file.write("function_call(%s, %s, %s)\n" 163 % (repr(callee_name), 164 repr(this_fun.decl.name), 165 repr(str(loc)))) 166 elif self.fn_is_python_ignorable(fndecl): 167 # Call to tp_dealloc. 168 pass 169 elif (isinstance(fndecl, gcc.SsaName) 170 and isinstance(fndecl.var, gcc.ParmDecl)): 171 # We can ignore an indirect call via a parameter to the 172 # current function, because this is handled via the rule 173 # for passthrough functions. 174 pass 175 else: 176 # Any other indirect call. 177 self.output_file.write("has_indirect_call(%s, %s)\n" 178 % (repr(this_fun.decl.name), 179 repr(str(loc)))) 180 return callee_name 181 182 # This does most of the work for examine_one_bb. 183 # THIS_FUN is the enclosing function. 184 # BB is the basic block to process. 185 # Returns True if this block is the header of a TRY_CATCH, False 186 # otherwise. 187 def examine_one_bb_inner(self, this_fun, bb): 188 if not bb.gimple: 189 return False 190 try_catch = False 191 for stmt in bb.gimple: 192 loc = stmt.loc 193 if not loc: 194 loc = this_fun.decl.location 195 if not isinstance(stmt, gcc.GimpleCall): 196 continue 197 callee_name = self.handle_one_fndecl(this_fun, stmt.fn, loc) 198 199 if callee_name == 'exceptions_state_mc_action_iter': 200 try_catch = True 201 202 global non_passthrough_functions 203 if callee_name in non_passthrough_functions: 204 continue 205 206 # We have to specially handle calls where an argument to 207 # the call is itself a function, e.g., qsort. In general 208 # we model these as "passthrough" -- we assume that in 209 # addition to the call the qsort there is also a call to 210 # the argument function. 211 for arg in stmt.args: 212 # We are only interested in arguments which are functions. 213 t = arg.type 214 if isinstance(t, gcc.PointerType): 215 t = t.dereference 216 if not isinstance(t, gcc.FunctionType): 217 continue 218 219 if isinstance(arg, gcc.AddrExpr): 220 arg = arg.operand 221 222 global cleanup_functions 223 if callee_name in cleanup_functions: 224 if not isinstance(arg, gcc.FunctionDecl): 225 gcc.inform(loc, 'cleanup argument not a DECL: %s' % repr(arg)) 226 else: 227 # Cleanups must be nothrow. 228 self.output_file.write("declare_cleanup(%s)\n" 229 % repr(str(arg.name))) 230 else: 231 # Assume we have a passthrough function, like 232 # qsort or an iterator. We model this by 233 # pretending there is an ordinary call at this 234 # point. 235 self.handle_one_fndecl(this_fun, arg, loc) 236 return try_catch 237 238 # Examine all the calls in a basic block and generate output for 239 # them. 240 # THIS_FUN is the enclosing function. 241 # BB is the basic block to examine. 242 # BB_WORKLIST is a list of basic blocks to work on; we add the 243 # appropriate successor blocks to this. 244 # SEEN_BBS is a map whose keys are basic blocks we have already 245 # processed. We use this to ensure that we only visit a given 246 # block once. 247 def examine_one_bb(self, this_fun, bb, bb_worklist, seen_bbs): 248 try_catch = self.examine_one_bb_inner(this_fun, bb) 249 for edge in bb.succs: 250 if edge.dest in seen_bbs: 251 continue 252 seen_bbs[edge.dest] = 1 253 if try_catch: 254 # This is bogus, but we magically know the right 255 # answer. 256 if edge.false_value: 257 bb_worklist.append(edge.dest) 258 else: 259 bb_worklist.append(edge.dest) 260 261 # Iterate over all basic blocks in THIS_FUN. 262 def iterate_bbs(self, this_fun): 263 # Iteration must be in control-flow order, because if we see a 264 # TRY_CATCH construct we need to drop all the contained blocks. 265 bb_worklist = [this_fun.cfg.entry] 266 seen_bbs = {} 267 seen_bbs[this_fun.cfg.entry] = 1 268 for bb in bb_worklist: 269 self.examine_one_bb(this_fun, bb, bb_worklist, seen_bbs) 270 271 def execute(self, fun): 272 if fun and fun.cfg and fun.decl: 273 self.output_file.write("################\n") 274 self.output_file.write("# Analysis for %s\n" % fun.decl.name) 275 self.output_file.write("define_function(%s, %s)\n" 276 % (repr(fun.decl.name), 277 repr(str(fun.decl.location)))) 278 279 global ignore_functions 280 if fun.decl.name not in ignore_functions: 281 self.iterate_bbs(fun) 282 283def main(**kwargs): 284 global output_file 285 output_file = open(gcc.get_dump_base_name() + '.gdb_exc.py', 'w') 286 # We used to use attributes here, but there didn't seem to be a 287 # big benefit over hard-coding. 288 output_file.write('declare_throw("throw_exception")\n') 289 output_file.write('declare_throw("throw_verror")\n') 290 output_file.write('declare_throw("throw_vfatal")\n') 291 output_file.write('declare_throw("throw_error")\n') 292 gcc.register_callback(gcc.PLUGIN_FINISH_UNIT, close_output) 293 ps = GdbExceptionChecker(output_file) 294 ps.register_after('ssa') 295 296main() 297