879 lines
26 KiB
C
879 lines
26 KiB
C
/* 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 "alias.h"
|
|
#include "symtab.h"
|
|
#include "options.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 "calls.h"
|
|
#include "tree-ssa-alias.h"
|
|
#include "predict.h"
|
|
#include "basic-block.h"
|
|
#include "gimple.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;
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|