GDB (xrefs)
excheck.py
Go to the documentation of this file.
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 
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 
32 import gcc
33 import gccutils
34 import sys
35 
36 # Where our output goes.
37 output_file = None
38 
39 # Cleanup functions require special treatment, because they take a
40 # function argument, but in theory the function must be nothrow.
41 cleanup_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.
50 ignore_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.
65 non_passthrough_functions = {
66  'signal': 1,
67  'add_internal_function': 1
68 }
69 
70 # Return True if the type is from Python.
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.
81 def 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.
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.
115 def close_output(*args):
116  global output_file
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.
125 class 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 
283 def 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 
296 main()
def iterate_bbs(self, this_fun)
Definition: excheck.py:262
def examine_globals()
Definition: excheck.py:93
def log(self, obj)
Definition: excheck.py:130
def examine_one_bb_inner(self, this_fun, bb)
Definition: excheck.py:187
def close_output(args)
Definition: excheck.py:115
def examine_struct_fields(initializer)
Definition: excheck.py:81
def execute(self, fun)
Definition: excheck.py:271
def main(kwargs)
Definition: excheck.py:283
def examine_one_bb(self, this_fun, bb, bb_worklist, seen_bbs)
Definition: excheck.py:247
def handle_one_fndecl(self, this_fun, fndecl, loc)
Definition: excheck.py:155
def fn_is_python_ignorable(self, fn)
Definition: excheck.py:135
def __init__(self, output_file)
Definition: excheck.py:126
def type_is_pythonic(t)
Definition: excheck.py:71