| /* Pointer Bounds Checker IPA passes. |
| Copyright (C) 2014-2015 Free Software Foundation, Inc. |
| Contributed by Ilya Enkovich (ilya.enkovich@intel.com) |
| |
| This file is part of GCC. |
| |
| GCC 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, or (at your option) any later |
| version. |
| |
| GCC 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 GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "hash-set.h" |
| #include "machmode.h" |
| #include "vec.h" |
| #include "double-int.h" |
| #include "input.h" |
| #include "alias.h" |
| #include "symtab.h" |
| #include "options.h" |
| #include "wide-int.h" |
| #include "inchash.h" |
| #include "tree.h" |
| #include "fold-const.h" |
| #include "stor-layout.h" |
| #include "tree-pass.h" |
| #include "stringpool.h" |
| #include "bitmap.h" |
| #include "gimple-expr.h" |
| #include "tm.h" |
| #include "hard-reg-set.h" |
| #include "function.h" |
| #include "is-a.h" |
| #include "tree-ssa-alias.h" |
| #include "predict.h" |
| #include "basic-block.h" |
| #include "gimple.h" |
| #include "ipa-ref.h" |
| #include "lto-streamer.h" |
| #include "cgraph.h" |
| #include "tree-chkp.h" |
| #include "tree-inline.h" |
| #include "ipa-chkp.h" |
| |
| /* Pointer Bounds Checker has two IPA passes to support code instrumentation. |
| |
| In instrumented code each pointer is provided with bounds. For input |
| pointer parameters it means we also have bounds passed. For calls it |
| means we have additional bounds arguments for pointer arguments. |
| |
| To have all IPA optimizations working correctly we have to express |
| dataflow between passed and received bounds explicitly via additional |
| entries in function declaration arguments list and in function type. |
| Since we may have both instrumented and not instrumented code at the |
| same time, we cannot replace all original functions with their |
| instrumented variants. Therefore we create clones (versions) instead. |
| |
| Instrumentation clones creation is a separate IPA pass which is a part |
| of early local passes. Clones are created after SSA is built (because |
| instrumentation pass works on SSA) and before any transformations |
| which may change pointer flow and therefore lead to incorrect code |
| instrumentation (possibly causing false bounds check failures). |
| |
| Instrumentation clones have pointer bounds arguments added right after |
| pointer arguments. Clones have assembler name of the original |
| function with suffix added. New assembler name is in transparent |
| alias chain with the original name. Thus we expect all calls to the |
| original and instrumented functions look similar in assembler. |
| |
| During instrumentation versioning pass we create instrumented versions |
| of all function with body and also for all their aliases and thunks. |
| Clones for functions with no body are created on demand (usually |
| during call instrumentation). |
| |
| Original and instrumented function nodes are connected with IPA |
| reference IPA_REF_CHKP. It is mostly done to have reachability |
| analysis working correctly. We may have no references to the |
| instrumented function in the code but it still should be counted |
| as reachable if the original function is reachable. |
| |
| When original function bodies are not needed anymore we release |
| them and transform functions into a special kind of thunks. Each |
| thunk has a call edge to the instrumented version. These thunks |
| help to keep externally visible instrumented functions visible |
| when linker resolution files are used. Linker has no info about |
| connection between original and instrumented function and |
| therefore we may wrongly decide (due to difference in assembler |
| names) that instrumented function version is local and can be |
| removed. */ |
| |
| #define CHKP_BOUNDS_OF_SYMBOL_PREFIX "__chkp_bounds_of_" |
| #define CHKP_WRAPPER_SYMBOL_PREFIX "__mpx_wrapper_" |
| |
| /* Return 1 calls to FNDECL should be replaced with |
| a call to wrapper function. */ |
| bool |
| chkp_wrap_function (tree fndecl) |
| { |
| if (!flag_chkp_use_wrappers) |
| return false; |
| |
| if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL) |
| { |
| switch (DECL_FUNCTION_CODE (fndecl)) |
| { |
| case BUILT_IN_STRLEN: |
| case BUILT_IN_STRCPY: |
| case BUILT_IN_STRNCPY: |
| case BUILT_IN_STPCPY: |
| case BUILT_IN_STPNCPY: |
| case BUILT_IN_STRCAT: |
| case BUILT_IN_STRNCAT: |
| case BUILT_IN_MEMCPY: |
| case BUILT_IN_MEMPCPY: |
| case BUILT_IN_MEMSET: |
| case BUILT_IN_MEMMOVE: |
| case BUILT_IN_BZERO: |
| case BUILT_IN_MALLOC: |
| case BUILT_IN_CALLOC: |
| case BUILT_IN_REALLOC: |
| return 1; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| return false; |
| } |
| |
| static const char * |
| chkp_wrap_function_name (tree fndecl) |
| { |
| gcc_assert (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL); |
| |
| switch (DECL_FUNCTION_CODE (fndecl)) |
| { |
| case BUILT_IN_STRLEN: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "strlen"; |
| case BUILT_IN_STRCPY: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "strcpy"; |
| case BUILT_IN_STRNCPY: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "strncpy"; |
| case BUILT_IN_STPCPY: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "stpcpy"; |
| case BUILT_IN_STPNCPY: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "stpncpy"; |
| case BUILT_IN_STRCAT: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "strcat"; |
| case BUILT_IN_STRNCAT: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "strncat"; |
| case BUILT_IN_MEMCPY: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "memcpy"; |
| case BUILT_IN_MEMPCPY: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "mempcpy"; |
| case BUILT_IN_MEMSET: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "memset"; |
| case BUILT_IN_MEMMOVE: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "memmove"; |
| case BUILT_IN_BZERO: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "bzero"; |
| case BUILT_IN_MALLOC: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "malloc"; |
| case BUILT_IN_CALLOC: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "calloc"; |
| case BUILT_IN_REALLOC: |
| return CHKP_WRAPPER_SYMBOL_PREFIX "realloc"; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| return ""; |
| } |
| |
| /* Build a clone of FNDECL with a modified name. */ |
| |
| static tree |
| chkp_build_instrumented_fndecl (tree fndecl) |
| { |
| tree new_decl = copy_node (fndecl); |
| tree new_name; |
| std::string s; |
| |
| /* called_as_built_in checks DECL_NAME to identify calls to |
| builtins. We want instrumented calls to builtins to be |
| recognized by called_as_built_in. Therefore use original |
| DECL_NAME for cloning with no prefixes. */ |
| s = IDENTIFIER_POINTER (DECL_NAME (fndecl)); |
| s += ".chkp"; |
| DECL_NAME (new_decl) = get_identifier (s.c_str ()); |
| |
| /* References to the original and to the instrumented version |
| should look the same in the output assembly. And we cannot |
| use the same assembler name for the instrumented version |
| because it conflicts with decl merging algorithms in LTO. |
| Achieve the result by using transparent alias name for the |
| instrumented version. */ |
| if (chkp_wrap_function(fndecl)) |
| { |
| new_name = get_identifier (chkp_wrap_function_name (fndecl)); |
| DECL_VISIBILITY (new_decl) = VISIBILITY_DEFAULT; |
| } |
| else |
| { |
| s = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (fndecl)); |
| s += ".chkp"; |
| new_name = get_identifier (s.c_str ()); |
| IDENTIFIER_TRANSPARENT_ALIAS (new_name) = 1; |
| TREE_CHAIN (new_name) = DECL_ASSEMBLER_NAME (fndecl); |
| } |
| SET_DECL_ASSEMBLER_NAME (new_decl, new_name); |
| |
| /* For functions with body versioning will make a copy of arguments. |
| For functions with no body we need to do it here. */ |
| if (!gimple_has_body_p (fndecl)) |
| DECL_ARGUMENTS (new_decl) = copy_list (DECL_ARGUMENTS (fndecl)); |
| |
| /* We are going to modify attributes list and therefore should |
| make own copy. */ |
| DECL_ATTRIBUTES (new_decl) = copy_list (DECL_ATTRIBUTES (fndecl)); |
| |
| /* Change builtin function code. */ |
| if (DECL_BUILT_IN (new_decl)) |
| { |
| gcc_assert (DECL_BUILT_IN_CLASS (new_decl) == BUILT_IN_NORMAL); |
| gcc_assert (DECL_FUNCTION_CODE (new_decl) < BEGIN_CHKP_BUILTINS); |
| DECL_FUNCTION_CODE (new_decl) |
| = (enum built_in_function)(DECL_FUNCTION_CODE (new_decl) |
| + BEGIN_CHKP_BUILTINS + 1); |
| } |
| |
| return new_decl; |
| } |
| |
| |
| /* Fix operands of attribute from ATTRS list named ATTR_NAME. |
| Integer operands are replaced with values according to |
| INDEXES map having LEN elements. For operands out of len |
| we just add DELTA. */ |
| |
| static void |
| chkp_map_attr_arg_indexes (tree attrs, const char *attr_name, |
| unsigned *indexes, int len, int delta) |
| { |
| tree attr = lookup_attribute (attr_name, attrs); |
| tree op; |
| |
| if (!attr) |
| return; |
| |
| TREE_VALUE (attr) = copy_list (TREE_VALUE (attr)); |
| for (op = TREE_VALUE (attr); op; op = TREE_CHAIN (op)) |
| { |
| int idx; |
| |
| if (TREE_CODE (TREE_VALUE (op)) != INTEGER_CST) |
| continue; |
| |
| idx = TREE_INT_CST_LOW (TREE_VALUE (op)); |
| |
| /* If idx exceeds indexes length then we just |
| keep it at the same distance from the last |
| known arg. */ |
| if (idx > len) |
| idx += delta; |
| else |
| idx = indexes[idx - 1] + 1; |
| TREE_VALUE (op) = build_int_cst (TREE_TYPE (TREE_VALUE (op)), idx); |
| } |
| } |
| |
| /* Make a copy of function type ORIG_TYPE adding pointer |
| bounds as additional arguments. */ |
| |
| tree |
| chkp_copy_function_type_adding_bounds (tree orig_type) |
| { |
| tree type; |
| tree arg_type, attrs, t; |
| unsigned len = list_length (TYPE_ARG_TYPES (orig_type)); |
| unsigned *indexes = XALLOCAVEC (unsigned, len); |
| unsigned idx = 0, new_idx = 0; |
| |
| for (arg_type = TYPE_ARG_TYPES (orig_type); |
| arg_type; |
| arg_type = TREE_CHAIN (arg_type)) |
| if (TREE_VALUE (arg_type) == void_type_node) |
| continue; |
| else if (BOUNDED_TYPE_P (TREE_VALUE (arg_type)) |
| || pass_by_reference (NULL, TYPE_MODE (TREE_VALUE (arg_type)), |
| TREE_VALUE (arg_type), true) |
| || chkp_type_has_pointer (TREE_VALUE (arg_type))) |
| break; |
| |
| /* We may use original type if there are no bounds passed. */ |
| if (!arg_type) |
| return orig_type; |
| |
| type = build_distinct_type_copy (orig_type); |
| TYPE_ARG_TYPES (type) = copy_list (TYPE_ARG_TYPES (type)); |
| |
| for (arg_type = TYPE_ARG_TYPES (type); |
| arg_type; |
| arg_type = TREE_CHAIN (arg_type)) |
| { |
| indexes[idx++] = new_idx++; |
| |
| /* pass_by_reference returns 1 for void type, |
| so check for it first. */ |
| if (TREE_VALUE (arg_type) == void_type_node) |
| continue; |
| else if (BOUNDED_TYPE_P (TREE_VALUE (arg_type)) |
| || pass_by_reference (NULL, TYPE_MODE (TREE_VALUE (arg_type)), |
| TREE_VALUE (arg_type), true)) |
| { |
| tree new_type = build_tree_list (NULL_TREE, |
| pointer_bounds_type_node); |
| TREE_CHAIN (new_type) = TREE_CHAIN (arg_type); |
| TREE_CHAIN (arg_type) = new_type; |
| |
| arg_type = TREE_CHAIN (arg_type); |
| new_idx++; |
| } |
| else if (chkp_type_has_pointer (TREE_VALUE (arg_type))) |
| { |
| bitmap slots = BITMAP_ALLOC (NULL); |
| bitmap_iterator bi; |
| unsigned bnd_no; |
| |
| chkp_find_bound_slots (TREE_VALUE (arg_type), slots); |
| |
| EXECUTE_IF_SET_IN_BITMAP (slots, 0, bnd_no, bi) |
| { |
| tree new_type = build_tree_list (NULL_TREE, |
| pointer_bounds_type_node); |
| TREE_CHAIN (new_type) = TREE_CHAIN (arg_type); |
| TREE_CHAIN (arg_type) = new_type; |
| |
| arg_type = TREE_CHAIN (arg_type); |
| new_idx++; |
| } |
| BITMAP_FREE (slots); |
| } |
| } |
| |
| /* If function type has attribute with arg indexes then |
| we have to copy it fixing attribute ops. Map for |
| fixing is in indexes array. */ |
| attrs = TYPE_ATTRIBUTES (type); |
| if (lookup_attribute ("nonnull", attrs) |
| || lookup_attribute ("format", attrs) |
| || lookup_attribute ("format_arg", attrs)) |
| { |
| int delta = new_idx - len; |
| attrs = copy_list (TYPE_ATTRIBUTES (type)); |
| chkp_map_attr_arg_indexes (attrs, "nonnull", indexes, len, delta); |
| chkp_map_attr_arg_indexes (attrs, "format", indexes, len, delta); |
| chkp_map_attr_arg_indexes (attrs, "format_arg", indexes, len, delta); |
| TYPE_ATTRIBUTES (type) = attrs; |
| } |
| |
| t = TYPE_MAIN_VARIANT (orig_type); |
| if (orig_type != t) |
| { |
| TYPE_MAIN_VARIANT (type) = t; |
| TYPE_NEXT_VARIANT (type) = TYPE_NEXT_VARIANT (t); |
| TYPE_NEXT_VARIANT (t) = type; |
| } |
| else |
| { |
| TYPE_MAIN_VARIANT (type) = type; |
| TYPE_NEXT_VARIANT (type) = NULL; |
| } |
| |
| |
| return type; |
| } |
| |
| /* For given function FNDECL add bounds arguments to arguments |
| list. */ |
| |
| static void |
| chkp_add_bounds_params_to_function (tree fndecl) |
| { |
| tree arg; |
| |
| for (arg = DECL_ARGUMENTS (fndecl); arg; arg = DECL_CHAIN (arg)) |
| if (BOUNDED_P (arg)) |
| { |
| std::string new_name = CHKP_BOUNDS_OF_SYMBOL_PREFIX; |
| tree new_arg; |
| |
| if (DECL_NAME (arg)) |
| new_name += IDENTIFIER_POINTER (DECL_NAME (arg)); |
| else |
| { |
| char uid[25]; |
| snprintf (uid, 25, "D.%u", DECL_UID (arg)); |
| new_name += uid; |
| } |
| |
| new_arg = build_decl (DECL_SOURCE_LOCATION (arg), PARM_DECL, |
| get_identifier (new_name.c_str ()), |
| pointer_bounds_type_node); |
| DECL_ARG_TYPE (new_arg) = pointer_bounds_type_node; |
| DECL_CONTEXT (new_arg) = DECL_CONTEXT (arg); |
| DECL_ARTIFICIAL (new_arg) = 1; |
| DECL_CHAIN (new_arg) = DECL_CHAIN (arg); |
| DECL_CHAIN (arg) = new_arg; |
| |
| arg = DECL_CHAIN (arg); |
| |
| } |
| else if (chkp_type_has_pointer (TREE_TYPE (arg))) |
| { |
| tree orig_arg = arg; |
| bitmap slots = BITMAP_ALLOC (NULL); |
| bitmap_iterator bi; |
| unsigned bnd_no; |
| |
| chkp_find_bound_slots (TREE_TYPE (arg), slots); |
| |
| EXECUTE_IF_SET_IN_BITMAP (slots, 0, bnd_no, bi) |
| { |
| std::string new_name = CHKP_BOUNDS_OF_SYMBOL_PREFIX; |
| tree new_arg; |
| char offs[25]; |
| |
| if (DECL_NAME (orig_arg)) |
| new_name += IDENTIFIER_POINTER (DECL_NAME (orig_arg)); |
| else |
| { |
| snprintf (offs, 25, "D.%u", DECL_UID (arg)); |
| new_name += offs; |
| } |
| snprintf (offs, 25, "__%u", bnd_no * POINTER_SIZE / BITS_PER_UNIT); |
| |
| new_arg = build_decl (DECL_SOURCE_LOCATION (orig_arg), |
| PARM_DECL, |
| get_identifier (new_name.c_str ()), |
| pointer_bounds_type_node); |
| DECL_ARG_TYPE (new_arg) = pointer_bounds_type_node; |
| DECL_CONTEXT (new_arg) = DECL_CONTEXT (orig_arg); |
| DECL_ARTIFICIAL (new_arg) = 1; |
| DECL_CHAIN (new_arg) = DECL_CHAIN (arg); |
| DECL_CHAIN (arg) = new_arg; |
| |
| arg = DECL_CHAIN (arg); |
| } |
| BITMAP_FREE (slots); |
| } |
| |
| TREE_TYPE (fndecl) = |
| chkp_copy_function_type_adding_bounds (TREE_TYPE (fndecl)); |
| } |
| |
| /* Return an instrumentation clone for builtin function |
| FNDECL. Create one if needed. */ |
| |
| tree |
| chkp_maybe_clone_builtin_fndecl (tree fndecl) |
| { |
| tree clone; |
| enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); |
| |
| gcc_assert (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && fcode < BEGIN_CHKP_BUILTINS); |
| |
| fcode = (enum built_in_function) (fcode + BEGIN_CHKP_BUILTINS + 1); |
| clone = builtin_decl_explicit (fcode); |
| if (clone) |
| return clone; |
| |
| clone = chkp_build_instrumented_fndecl (fndecl); |
| chkp_add_bounds_params_to_function (clone); |
| |
| gcc_assert (DECL_FUNCTION_CODE (clone) == fcode); |
| |
| set_builtin_decl (fcode, clone, false); |
| |
| return clone; |
| } |
| |
| /* Return 1 if function FNDECL should be instrumented. */ |
| |
| bool |
| chkp_instrumentable_p (tree fndecl) |
| { |
| struct function *fn = DECL_STRUCT_FUNCTION (fndecl); |
| return (!lookup_attribute ("bnd_legacy", DECL_ATTRIBUTES (fndecl)) |
| && (!flag_chkp_instrument_marked_only |
| || lookup_attribute ("bnd_instrument", DECL_ATTRIBUTES (fndecl))) |
| && (!fn || !copy_forbidden (fn, fndecl))); |
| } |
| |
| /* Return clone created for instrumentation of NODE or NULL. */ |
| |
| cgraph_node * |
| chkp_maybe_create_clone (tree fndecl) |
| { |
| cgraph_node *node = cgraph_node::get_create (fndecl); |
| cgraph_node *clone = node->instrumented_version; |
| |
| gcc_assert (!node->instrumentation_clone); |
| |
| if (DECL_BUILT_IN (fndecl) |
| && (DECL_BUILT_IN_CLASS (fndecl) != BUILT_IN_NORMAL |
| || DECL_FUNCTION_CODE (fndecl) >= BEGIN_CHKP_BUILTINS)) |
| return NULL; |
| |
| clone = node->instrumented_version; |
| |
| /* Some instrumented builtin function calls may be optimized and |
| cgraph nodes may be removed as unreachable. Later optimizations |
| may generate new calls to removed functions and in this case |
| we have to recreate cgraph node. FUNCTION_DECL for instrumented |
| builtin still exists and should be reused in such case. */ |
| if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && fndecl == builtin_decl_explicit (DECL_FUNCTION_CODE (fndecl)) |
| && !clone) |
| { |
| enum built_in_function fncode = DECL_FUNCTION_CODE (fndecl); |
| tree new_decl; |
| |
| fncode = (enum built_in_function) (fncode + BEGIN_CHKP_BUILTINS + 1); |
| new_decl = builtin_decl_explicit (fncode); |
| |
| /* We've actually already created an instrumented clone once. |
| Restore it. */ |
| if (new_decl) |
| { |
| clone = cgraph_node::get (new_decl); |
| |
| if (!clone) |
| { |
| gcc_assert (!gimple_has_body_p (fndecl)); |
| clone = cgraph_node::get_create (new_decl); |
| clone->externally_visible = node->externally_visible; |
| clone->local = node->local; |
| clone->address_taken = node->address_taken; |
| clone->thunk = node->thunk; |
| clone->alias = node->alias; |
| clone->weakref = node->weakref; |
| clone->cpp_implicit_alias = node->cpp_implicit_alias; |
| clone->orig_decl = fndecl; |
| clone->instrumentation_clone = true; |
| } |
| |
| clone->instrumented_version = node; |
| node->instrumented_version = clone; |
| } |
| } |
| |
| if (!clone) |
| { |
| tree new_decl = chkp_build_instrumented_fndecl (fndecl); |
| struct cgraph_edge *e; |
| struct ipa_ref *ref; |
| int i; |
| |
| clone = node->create_version_clone (new_decl, vNULL, NULL); |
| clone->externally_visible = node->externally_visible; |
| clone->local = node->local; |
| clone->address_taken = node->address_taken; |
| clone->thunk = node->thunk; |
| clone->alias = node->alias; |
| clone->weakref = node->weakref; |
| clone->cpp_implicit_alias = node->cpp_implicit_alias; |
| clone->instrumented_version = node; |
| clone->orig_decl = fndecl; |
| clone->instrumentation_clone = true; |
| node->instrumented_version = clone; |
| |
| if (gimple_has_body_p (fndecl)) |
| { |
| gcc_assert (chkp_instrumentable_p (fndecl)); |
| tree_function_versioning (fndecl, new_decl, NULL, false, |
| NULL, false, NULL, NULL); |
| clone->lowered = true; |
| } |
| |
| /* New params are inserted after versioning because it |
| actually copies args list from the original decl. */ |
| chkp_add_bounds_params_to_function (new_decl); |
| |
| /* Remember builtin fndecl. */ |
| if (DECL_BUILT_IN_CLASS (clone->decl) == BUILT_IN_NORMAL |
| && fndecl == builtin_decl_explicit (DECL_FUNCTION_CODE (fndecl))) |
| { |
| gcc_assert (!builtin_decl_explicit (DECL_FUNCTION_CODE (clone->decl))); |
| set_builtin_decl (DECL_FUNCTION_CODE (clone->decl), |
| clone->decl, false); |
| } |
| |
| /* Clones have the same comdat group as originals. */ |
| if (node->same_comdat_group |
| || (DECL_ONE_ONLY (node->decl) |
| && !DECL_EXTERNAL (node->decl))) |
| clone->add_to_same_comdat_group (node); |
| |
| if (gimple_has_body_p (fndecl)) |
| symtab->call_cgraph_insertion_hooks (clone); |
| |
| /* Clone all aliases. */ |
| for (i = 0; node->iterate_direct_aliases (i, ref); i++) |
| chkp_maybe_create_clone (ref->referring->decl); |
| |
| /* Clone all thunks. */ |
| for (e = node->callers; e; e = e->next_caller) |
| if (e->caller->thunk.thunk_p |
| && !e->caller->thunk.add_pointer_bounds_args |
| && !e->caller->instrumentation_clone) |
| { |
| struct cgraph_node *thunk |
| = chkp_maybe_create_clone (e->caller->decl); |
| /* Redirect thunk clone edge to the node clone. */ |
| thunk->callees->redirect_callee (clone); |
| } |
| |
| /* For aliases and thunks we should make sure target is cloned |
| to have proper references and edges. */ |
| if (node->thunk.thunk_p) |
| chkp_maybe_create_clone (node->callees->callee->decl); |
| else if (node->alias) |
| { |
| struct cgraph_node *target; |
| |
| ref = node->ref_list.first_reference (); |
| if (ref) |
| { |
| target = chkp_maybe_create_clone (ref->referred->decl); |
| clone->create_reference (target, IPA_REF_ALIAS); |
| } |
| |
| if (node->alias_target) |
| { |
| if (TREE_CODE (node->alias_target) == FUNCTION_DECL) |
| { |
| target = chkp_maybe_create_clone (node->alias_target); |
| clone->alias_target = target->decl; |
| } |
| else |
| clone->alias_target = node->alias_target; |
| } |
| } |
| |
| /* Add IPA reference. It's main role is to keep instrumented |
| version reachable while original node is reachable. */ |
| ref = node->create_reference (clone, IPA_REF_CHKP, NULL); |
| } |
| |
| return clone; |
| } |
| |
| /* Create clone for all functions to be instrumented. */ |
| |
| static unsigned int |
| chkp_versioning (void) |
| { |
| struct cgraph_node *node; |
| const char *reason; |
| |
| bitmap_obstack_initialize (NULL); |
| |
| FOR_EACH_DEFINED_FUNCTION (node) |
| { |
| if (!node->instrumentation_clone |
| && !node->instrumented_version |
| && !node->alias |
| && !node->thunk.thunk_p |
| && (!DECL_BUILT_IN (node->decl) |
| || (DECL_BUILT_IN_CLASS (node->decl) == BUILT_IN_NORMAL |
| && DECL_FUNCTION_CODE (node->decl) < BEGIN_CHKP_BUILTINS))) |
| { |
| if (chkp_instrumentable_p (node->decl)) |
| chkp_maybe_create_clone (node->decl); |
| else if ((reason = copy_forbidden (DECL_STRUCT_FUNCTION (node->decl), |
| node->decl))) |
| { |
| if (warning_at (DECL_SOURCE_LOCATION (node->decl), OPT_Wchkp, |
| "function cannot be instrumented")) |
| inform (DECL_SOURCE_LOCATION (node->decl), reason, node->decl); |
| } |
| } |
| } |
| |
| /* Mark all aliases and thunks of functions with no instrumented |
| version as legacy function. */ |
| FOR_EACH_DEFINED_FUNCTION (node) |
| { |
| if (!node->instrumentation_clone |
| && !node->instrumented_version |
| && (node->alias || node->thunk.thunk_p) |
| && !lookup_attribute ("bnd_legacy", DECL_ATTRIBUTES (node->decl))) |
| DECL_ATTRIBUTES (node->decl) |
| = tree_cons (get_identifier ("bnd_legacy"), NULL, |
| DECL_ATTRIBUTES (node->decl)); |
| } |
| |
| bitmap_obstack_release (NULL); |
| |
| return 0; |
| } |
| |
| /* In this pass we remove bodies of functions having |
| instrumented version. Functions with removed bodies |
| become a special kind of thunks to provide a connection |
| between calls to the original version and instrumented |
| function. */ |
| |
| static unsigned int |
| chkp_produce_thunks (bool early) |
| { |
| struct cgraph_node *node; |
| |
| FOR_EACH_DEFINED_FUNCTION (node) |
| { |
| if (!node->instrumentation_clone |
| && node->instrumented_version |
| && gimple_has_body_p (node->decl) |
| && gimple_has_body_p (node->instrumented_version->decl) |
| && (!lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl)) |
| || !early)) |
| { |
| node->release_body (); |
| node->remove_callees (); |
| node->remove_all_references (); |
| |
| node->thunk.thunk_p = true; |
| node->thunk.add_pointer_bounds_args = true; |
| node->create_edge (node->instrumented_version, NULL, |
| 0, CGRAPH_FREQ_BASE); |
| node->create_reference (node->instrumented_version, |
| IPA_REF_CHKP, NULL); |
| /* Thunk shouldn't be a cdtor. */ |
| DECL_STATIC_CONSTRUCTOR (node->decl) = 0; |
| DECL_STATIC_DESTRUCTOR (node->decl) = 0; |
| } |
| } |
| |
| /* Mark instrumentation clones created for aliases and thunks |
| as insttrumented so they could be removed as unreachable |
| now. */ |
| if (!early) |
| { |
| FOR_EACH_DEFINED_FUNCTION (node) |
| { |
| if (node->instrumentation_clone |
| && (node->alias || node->thunk.thunk_p) |
| && !chkp_function_instrumented_p (node->decl)) |
| chkp_function_mark_instrumented (node->decl); |
| } |
| } |
| |
| return TODO_remove_functions; |
| } |
| |
| const pass_data pass_data_ipa_chkp_versioning = |
| { |
| SIMPLE_IPA_PASS, /* type */ |
| "chkp_versioning", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_NONE, /* tv_id */ |
| 0, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0 /* todo_flags_finish */ |
| }; |
| |
| const pass_data pass_data_ipa_chkp_early_produce_thunks = |
| { |
| SIMPLE_IPA_PASS, /* type */ |
| "chkp_ecleanup", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_NONE, /* tv_id */ |
| 0, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0 /* todo_flags_finish */ |
| }; |
| |
| const pass_data pass_data_ipa_chkp_produce_thunks = |
| { |
| SIMPLE_IPA_PASS, /* type */ |
| "chkp_cleanup", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_NONE, /* tv_id */ |
| 0, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0 /* todo_flags_finish */ |
| }; |
| |
| class pass_ipa_chkp_versioning : public simple_ipa_opt_pass |
| { |
| public: |
| pass_ipa_chkp_versioning (gcc::context *ctxt) |
| : simple_ipa_opt_pass (pass_data_ipa_chkp_versioning, ctxt) |
| {} |
| |
| /* opt_pass methods: */ |
| virtual opt_pass * clone () |
| { |
| return new pass_ipa_chkp_versioning (m_ctxt); |
| } |
| |
| virtual bool gate (function *) |
| { |
| return flag_check_pointer_bounds; |
| } |
| |
| virtual unsigned int execute (function *) |
| { |
| return chkp_versioning (); |
| } |
| |
| }; // class pass_ipa_chkp_versioning |
| |
| class pass_ipa_chkp_early_produce_thunks : public simple_ipa_opt_pass |
| { |
| public: |
| pass_ipa_chkp_early_produce_thunks (gcc::context *ctxt) |
| : simple_ipa_opt_pass (pass_data_ipa_chkp_early_produce_thunks, ctxt) |
| {} |
| |
| /* opt_pass methods: */ |
| virtual opt_pass * clone () |
| { |
| return new pass_ipa_chkp_early_produce_thunks (m_ctxt); |
| } |
| |
| virtual bool gate (function *) |
| { |
| return flag_check_pointer_bounds; |
| } |
| |
| virtual unsigned int execute (function *) |
| { |
| return chkp_produce_thunks (true); |
| } |
| |
| }; // class pass_chkp_produce_thunks |
| |
| class pass_ipa_chkp_produce_thunks : public simple_ipa_opt_pass |
| { |
| public: |
| pass_ipa_chkp_produce_thunks (gcc::context *ctxt) |
| : simple_ipa_opt_pass (pass_data_ipa_chkp_produce_thunks, ctxt) |
| {} |
| |
| /* opt_pass methods: */ |
| virtual opt_pass * clone () |
| { |
| return new pass_ipa_chkp_produce_thunks (m_ctxt); |
| } |
| |
| virtual bool gate (function *) |
| { |
| return flag_check_pointer_bounds; |
| } |
| |
| virtual unsigned int execute (function *) |
| { |
| return chkp_produce_thunks (false); |
| } |
| |
| }; // class pass_chkp_produce_thunks |
| |
| simple_ipa_opt_pass * |
| make_pass_ipa_chkp_versioning (gcc::context *ctxt) |
| { |
| return new pass_ipa_chkp_versioning (ctxt); |
| } |
| |
| simple_ipa_opt_pass * |
| make_pass_ipa_chkp_early_produce_thunks (gcc::context *ctxt) |
| { |
| return new pass_ipa_chkp_early_produce_thunks (ctxt); |
| } |
| |
| simple_ipa_opt_pass * |
| make_pass_ipa_chkp_produce_thunks (gcc::context *ctxt) |
| { |
| return new pass_ipa_chkp_produce_thunks (ctxt); |
| } |