47a113429b
PR sanitizer/80308 * asan.c (asan_store_shadow_bytes): Fix location of last_chunk_value for big endian. * c-c++-common/asan/pr80308.c: New test. Co-Authored-By: Bernd Edlinger <bernd.edlinger@hotmail.de> From-SVN: r246703
3276 lines
104 KiB
C
3276 lines
104 KiB
C
/* AddressSanitizer, a fast memory error detector.
|
|
Copyright (C) 2012-2017 Free Software Foundation, Inc.
|
|
Contributed by Kostya Serebryany <kcc@google.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 "backend.h"
|
|
#include "target.h"
|
|
#include "rtl.h"
|
|
#include "tree.h"
|
|
#include "gimple.h"
|
|
#include "cfghooks.h"
|
|
#include "alloc-pool.h"
|
|
#include "tree-pass.h"
|
|
#include "memmodel.h"
|
|
#include "tm_p.h"
|
|
#include "ssa.h"
|
|
#include "stringpool.h"
|
|
#include "tree-ssanames.h"
|
|
#include "optabs.h"
|
|
#include "emit-rtl.h"
|
|
#include "cgraph.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "alias.h"
|
|
#include "fold-const.h"
|
|
#include "cfganal.h"
|
|
#include "gimplify.h"
|
|
#include "gimple-iterator.h"
|
|
#include "varasm.h"
|
|
#include "stor-layout.h"
|
|
#include "tree-iterator.h"
|
|
#include "asan.h"
|
|
#include "dojump.h"
|
|
#include "explow.h"
|
|
#include "expr.h"
|
|
#include "output.h"
|
|
#include "langhooks.h"
|
|
#include "cfgloop.h"
|
|
#include "gimple-builder.h"
|
|
#include "ubsan.h"
|
|
#include "params.h"
|
|
#include "builtins.h"
|
|
#include "fnmatch.h"
|
|
#include "tree-inline.h"
|
|
|
|
/* AddressSanitizer finds out-of-bounds and use-after-free bugs
|
|
with <2x slowdown on average.
|
|
|
|
The tool consists of two parts:
|
|
instrumentation module (this file) and a run-time library.
|
|
The instrumentation module adds a run-time check before every memory insn.
|
|
For a 8- or 16- byte load accessing address X:
|
|
ShadowAddr = (X >> 3) + Offset
|
|
ShadowValue = *(char*)ShadowAddr; // *(short*) for 16-byte access.
|
|
if (ShadowValue)
|
|
__asan_report_load8(X);
|
|
For a load of N bytes (N=1, 2 or 4) from address X:
|
|
ShadowAddr = (X >> 3) + Offset
|
|
ShadowValue = *(char*)ShadowAddr;
|
|
if (ShadowValue)
|
|
if ((X & 7) + N - 1 > ShadowValue)
|
|
__asan_report_loadN(X);
|
|
Stores are instrumented similarly, but using __asan_report_storeN functions.
|
|
A call too __asan_init_vN() is inserted to the list of module CTORs.
|
|
N is the version number of the AddressSanitizer API. The changes between the
|
|
API versions are listed in libsanitizer/asan/asan_interface_internal.h.
|
|
|
|
The run-time library redefines malloc (so that redzone are inserted around
|
|
the allocated memory) and free (so that reuse of free-ed memory is delayed),
|
|
provides __asan_report* and __asan_init_vN functions.
|
|
|
|
Read more:
|
|
http://code.google.com/p/address-sanitizer/wiki/AddressSanitizerAlgorithm
|
|
|
|
The current implementation supports detection of out-of-bounds and
|
|
use-after-free in the heap, on the stack and for global variables.
|
|
|
|
[Protection of stack variables]
|
|
|
|
To understand how detection of out-of-bounds and use-after-free works
|
|
for stack variables, lets look at this example on x86_64 where the
|
|
stack grows downward:
|
|
|
|
int
|
|
foo ()
|
|
{
|
|
char a[23] = {0};
|
|
int b[2] = {0};
|
|
|
|
a[5] = 1;
|
|
b[1] = 2;
|
|
|
|
return a[5] + b[1];
|
|
}
|
|
|
|
For this function, the stack protected by asan will be organized as
|
|
follows, from the top of the stack to the bottom:
|
|
|
|
Slot 1/ [red zone of 32 bytes called 'RIGHT RedZone']
|
|
|
|
Slot 2/ [8 bytes of red zone, that adds up to the space of 'a' to make
|
|
the next slot be 32 bytes aligned; this one is called Partial
|
|
Redzone; this 32 bytes alignment is an asan constraint]
|
|
|
|
Slot 3/ [24 bytes for variable 'a']
|
|
|
|
Slot 4/ [red zone of 32 bytes called 'Middle RedZone']
|
|
|
|
Slot 5/ [24 bytes of Partial Red Zone (similar to slot 2]
|
|
|
|
Slot 6/ [8 bytes for variable 'b']
|
|
|
|
Slot 7/ [32 bytes of Red Zone at the bottom of the stack, called
|
|
'LEFT RedZone']
|
|
|
|
The 32 bytes of LEFT red zone at the bottom of the stack can be
|
|
decomposed as such:
|
|
|
|
1/ The first 8 bytes contain a magical asan number that is always
|
|
0x41B58AB3.
|
|
|
|
2/ The following 8 bytes contains a pointer to a string (to be
|
|
parsed at runtime by the runtime asan library), which format is
|
|
the following:
|
|
|
|
"<function-name> <space> <num-of-variables-on-the-stack>
|
|
(<32-bytes-aligned-offset-in-bytes-of-variable> <space>
|
|
<length-of-var-in-bytes> ){n} "
|
|
|
|
where '(...){n}' means the content inside the parenthesis occurs 'n'
|
|
times, with 'n' being the number of variables on the stack.
|
|
|
|
3/ The following 8 bytes contain the PC of the current function which
|
|
will be used by the run-time library to print an error message.
|
|
|
|
4/ The following 8 bytes are reserved for internal use by the run-time.
|
|
|
|
The shadow memory for that stack layout is going to look like this:
|
|
|
|
- content of shadow memory 8 bytes for slot 7: 0xF1F1F1F1.
|
|
The F1 byte pattern is a magic number called
|
|
ASAN_STACK_MAGIC_LEFT and is a way for the runtime to know that
|
|
the memory for that shadow byte is part of a the LEFT red zone
|
|
intended to seat at the bottom of the variables on the stack.
|
|
|
|
- content of shadow memory 8 bytes for slots 6 and 5:
|
|
0xF4F4F400. The F4 byte pattern is a magic number
|
|
called ASAN_STACK_MAGIC_PARTIAL. It flags the fact that the
|
|
memory region for this shadow byte is a PARTIAL red zone
|
|
intended to pad a variable A, so that the slot following
|
|
{A,padding} is 32 bytes aligned.
|
|
|
|
Note that the fact that the least significant byte of this
|
|
shadow memory content is 00 means that 8 bytes of its
|
|
corresponding memory (which corresponds to the memory of
|
|
variable 'b') is addressable.
|
|
|
|
- content of shadow memory 8 bytes for slot 4: 0xF2F2F2F2.
|
|
The F2 byte pattern is a magic number called
|
|
ASAN_STACK_MAGIC_MIDDLE. It flags the fact that the memory
|
|
region for this shadow byte is a MIDDLE red zone intended to
|
|
seat between two 32 aligned slots of {variable,padding}.
|
|
|
|
- content of shadow memory 8 bytes for slot 3 and 2:
|
|
0xF4000000. This represents is the concatenation of
|
|
variable 'a' and the partial red zone following it, like what we
|
|
had for variable 'b'. The least significant 3 bytes being 00
|
|
means that the 3 bytes of variable 'a' are addressable.
|
|
|
|
- content of shadow memory 8 bytes for slot 1: 0xF3F3F3F3.
|
|
The F3 byte pattern is a magic number called
|
|
ASAN_STACK_MAGIC_RIGHT. It flags the fact that the memory
|
|
region for this shadow byte is a RIGHT red zone intended to seat
|
|
at the top of the variables of the stack.
|
|
|
|
Note that the real variable layout is done in expand_used_vars in
|
|
cfgexpand.c. As far as Address Sanitizer is concerned, it lays out
|
|
stack variables as well as the different red zones, emits some
|
|
prologue code to populate the shadow memory as to poison (mark as
|
|
non-accessible) the regions of the red zones and mark the regions of
|
|
stack variables as accessible, and emit some epilogue code to
|
|
un-poison (mark as accessible) the regions of red zones right before
|
|
the function exits.
|
|
|
|
[Protection of global variables]
|
|
|
|
The basic idea is to insert a red zone between two global variables
|
|
and install a constructor function that calls the asan runtime to do
|
|
the populating of the relevant shadow memory regions at load time.
|
|
|
|
So the global variables are laid out as to insert a red zone between
|
|
them. The size of the red zones is so that each variable starts on a
|
|
32 bytes boundary.
|
|
|
|
Then a constructor function is installed so that, for each global
|
|
variable, it calls the runtime asan library function
|
|
__asan_register_globals_with an instance of this type:
|
|
|
|
struct __asan_global
|
|
{
|
|
// Address of the beginning of the global variable.
|
|
const void *__beg;
|
|
|
|
// Initial size of the global variable.
|
|
uptr __size;
|
|
|
|
// Size of the global variable + size of the red zone. This
|
|
// size is 32 bytes aligned.
|
|
uptr __size_with_redzone;
|
|
|
|
// Name of the global variable.
|
|
const void *__name;
|
|
|
|
// Name of the module where the global variable is declared.
|
|
const void *__module_name;
|
|
|
|
// 1 if it has dynamic initialization, 0 otherwise.
|
|
uptr __has_dynamic_init;
|
|
|
|
// A pointer to struct that contains source location, could be NULL.
|
|
__asan_global_source_location *__location;
|
|
}
|
|
|
|
A destructor function that calls the runtime asan library function
|
|
_asan_unregister_globals is also installed. */
|
|
|
|
static unsigned HOST_WIDE_INT asan_shadow_offset_value;
|
|
static bool asan_shadow_offset_computed;
|
|
static vec<char *> sanitized_sections;
|
|
|
|
/* Set of variable declarations that are going to be guarded by
|
|
use-after-scope sanitizer. */
|
|
|
|
static hash_set<tree> *asan_handled_variables = NULL;
|
|
|
|
hash_set <tree> *asan_used_labels = NULL;
|
|
|
|
/* Sets shadow offset to value in string VAL. */
|
|
|
|
bool
|
|
set_asan_shadow_offset (const char *val)
|
|
{
|
|
char *endp;
|
|
|
|
errno = 0;
|
|
#ifdef HAVE_LONG_LONG
|
|
asan_shadow_offset_value = strtoull (val, &endp, 0);
|
|
#else
|
|
asan_shadow_offset_value = strtoul (val, &endp, 0);
|
|
#endif
|
|
if (!(*val != '\0' && *endp == '\0' && errno == 0))
|
|
return false;
|
|
|
|
asan_shadow_offset_computed = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Set list of user-defined sections that need to be sanitized. */
|
|
|
|
void
|
|
set_sanitized_sections (const char *sections)
|
|
{
|
|
char *pat;
|
|
unsigned i;
|
|
FOR_EACH_VEC_ELT (sanitized_sections, i, pat)
|
|
free (pat);
|
|
sanitized_sections.truncate (0);
|
|
|
|
for (const char *s = sections; *s; )
|
|
{
|
|
const char *end;
|
|
for (end = s; *end && *end != ','; ++end);
|
|
size_t len = end - s;
|
|
sanitized_sections.safe_push (xstrndup (s, len));
|
|
s = *end ? end + 1 : end;
|
|
}
|
|
}
|
|
|
|
bool
|
|
asan_mark_p (gimple *stmt, enum asan_mark_flags flag)
|
|
{
|
|
return (gimple_call_internal_p (stmt, IFN_ASAN_MARK)
|
|
&& tree_to_uhwi (gimple_call_arg (stmt, 0)) == flag);
|
|
}
|
|
|
|
bool
|
|
asan_sanitize_stack_p (void)
|
|
{
|
|
return ((flag_sanitize & SANITIZE_ADDRESS)
|
|
&& ASAN_STACK
|
|
&& !asan_no_sanitize_address_p ());
|
|
}
|
|
|
|
/* Checks whether section SEC should be sanitized. */
|
|
|
|
static bool
|
|
section_sanitized_p (const char *sec)
|
|
{
|
|
char *pat;
|
|
unsigned i;
|
|
FOR_EACH_VEC_ELT (sanitized_sections, i, pat)
|
|
if (fnmatch (pat, sec, FNM_PERIOD) == 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Returns Asan shadow offset. */
|
|
|
|
static unsigned HOST_WIDE_INT
|
|
asan_shadow_offset ()
|
|
{
|
|
if (!asan_shadow_offset_computed)
|
|
{
|
|
asan_shadow_offset_computed = true;
|
|
asan_shadow_offset_value = targetm.asan_shadow_offset ();
|
|
}
|
|
return asan_shadow_offset_value;
|
|
}
|
|
|
|
alias_set_type asan_shadow_set = -1;
|
|
|
|
/* Pointer types to 1, 2 or 4 byte integers in shadow memory. A separate
|
|
alias set is used for all shadow memory accesses. */
|
|
static GTY(()) tree shadow_ptr_types[3];
|
|
|
|
/* Decl for __asan_option_detect_stack_use_after_return. */
|
|
static GTY(()) tree asan_detect_stack_use_after_return;
|
|
|
|
/* Hashtable support for memory references used by gimple
|
|
statements. */
|
|
|
|
/* This type represents a reference to a memory region. */
|
|
struct asan_mem_ref
|
|
{
|
|
/* The expression of the beginning of the memory region. */
|
|
tree start;
|
|
|
|
/* The size of the access. */
|
|
HOST_WIDE_INT access_size;
|
|
};
|
|
|
|
object_allocator <asan_mem_ref> asan_mem_ref_pool ("asan_mem_ref");
|
|
|
|
/* Initializes an instance of asan_mem_ref. */
|
|
|
|
static void
|
|
asan_mem_ref_init (asan_mem_ref *ref, tree start, HOST_WIDE_INT access_size)
|
|
{
|
|
ref->start = start;
|
|
ref->access_size = access_size;
|
|
}
|
|
|
|
/* Allocates memory for an instance of asan_mem_ref into the memory
|
|
pool returned by asan_mem_ref_get_alloc_pool and initialize it.
|
|
START is the address of (or the expression pointing to) the
|
|
beginning of memory reference. ACCESS_SIZE is the size of the
|
|
access to the referenced memory. */
|
|
|
|
static asan_mem_ref*
|
|
asan_mem_ref_new (tree start, HOST_WIDE_INT access_size)
|
|
{
|
|
asan_mem_ref *ref = asan_mem_ref_pool.allocate ();
|
|
|
|
asan_mem_ref_init (ref, start, access_size);
|
|
return ref;
|
|
}
|
|
|
|
/* This builds and returns a pointer to the end of the memory region
|
|
that starts at START and of length LEN. */
|
|
|
|
tree
|
|
asan_mem_ref_get_end (tree start, tree len)
|
|
{
|
|
if (len == NULL_TREE || integer_zerop (len))
|
|
return start;
|
|
|
|
if (!ptrofftype_p (len))
|
|
len = convert_to_ptrofftype (len);
|
|
|
|
return fold_build2 (POINTER_PLUS_EXPR, TREE_TYPE (start), start, len);
|
|
}
|
|
|
|
/* Return a tree expression that represents the end of the referenced
|
|
memory region. Beware that this function can actually build a new
|
|
tree expression. */
|
|
|
|
tree
|
|
asan_mem_ref_get_end (const asan_mem_ref *ref, tree len)
|
|
{
|
|
return asan_mem_ref_get_end (ref->start, len);
|
|
}
|
|
|
|
struct asan_mem_ref_hasher : nofree_ptr_hash <asan_mem_ref>
|
|
{
|
|
static inline hashval_t hash (const asan_mem_ref *);
|
|
static inline bool equal (const asan_mem_ref *, const asan_mem_ref *);
|
|
};
|
|
|
|
/* Hash a memory reference. */
|
|
|
|
inline hashval_t
|
|
asan_mem_ref_hasher::hash (const asan_mem_ref *mem_ref)
|
|
{
|
|
return iterative_hash_expr (mem_ref->start, 0);
|
|
}
|
|
|
|
/* Compare two memory references. We accept the length of either
|
|
memory references to be NULL_TREE. */
|
|
|
|
inline bool
|
|
asan_mem_ref_hasher::equal (const asan_mem_ref *m1,
|
|
const asan_mem_ref *m2)
|
|
{
|
|
return operand_equal_p (m1->start, m2->start, 0);
|
|
}
|
|
|
|
static hash_table<asan_mem_ref_hasher> *asan_mem_ref_ht;
|
|
|
|
/* Returns a reference to the hash table containing memory references.
|
|
This function ensures that the hash table is created. Note that
|
|
this hash table is updated by the function
|
|
update_mem_ref_hash_table. */
|
|
|
|
static hash_table<asan_mem_ref_hasher> *
|
|
get_mem_ref_hash_table ()
|
|
{
|
|
if (!asan_mem_ref_ht)
|
|
asan_mem_ref_ht = new hash_table<asan_mem_ref_hasher> (10);
|
|
|
|
return asan_mem_ref_ht;
|
|
}
|
|
|
|
/* Clear all entries from the memory references hash table. */
|
|
|
|
static void
|
|
empty_mem_ref_hash_table ()
|
|
{
|
|
if (asan_mem_ref_ht)
|
|
asan_mem_ref_ht->empty ();
|
|
}
|
|
|
|
/* Free the memory references hash table. */
|
|
|
|
static void
|
|
free_mem_ref_resources ()
|
|
{
|
|
delete asan_mem_ref_ht;
|
|
asan_mem_ref_ht = NULL;
|
|
|
|
asan_mem_ref_pool.release ();
|
|
}
|
|
|
|
/* Return true iff the memory reference REF has been instrumented. */
|
|
|
|
static bool
|
|
has_mem_ref_been_instrumented (tree ref, HOST_WIDE_INT access_size)
|
|
{
|
|
asan_mem_ref r;
|
|
asan_mem_ref_init (&r, ref, access_size);
|
|
|
|
asan_mem_ref *saved_ref = get_mem_ref_hash_table ()->find (&r);
|
|
return saved_ref && saved_ref->access_size >= access_size;
|
|
}
|
|
|
|
/* Return true iff the memory reference REF has been instrumented. */
|
|
|
|
static bool
|
|
has_mem_ref_been_instrumented (const asan_mem_ref *ref)
|
|
{
|
|
return has_mem_ref_been_instrumented (ref->start, ref->access_size);
|
|
}
|
|
|
|
/* Return true iff access to memory region starting at REF and of
|
|
length LEN has been instrumented. */
|
|
|
|
static bool
|
|
has_mem_ref_been_instrumented (const asan_mem_ref *ref, tree len)
|
|
{
|
|
HOST_WIDE_INT size_in_bytes
|
|
= tree_fits_shwi_p (len) ? tree_to_shwi (len) : -1;
|
|
|
|
return size_in_bytes != -1
|
|
&& has_mem_ref_been_instrumented (ref->start, size_in_bytes);
|
|
}
|
|
|
|
/* Set REF to the memory reference present in a gimple assignment
|
|
ASSIGNMENT. Return true upon successful completion, false
|
|
otherwise. */
|
|
|
|
static bool
|
|
get_mem_ref_of_assignment (const gassign *assignment,
|
|
asan_mem_ref *ref,
|
|
bool *ref_is_store)
|
|
{
|
|
gcc_assert (gimple_assign_single_p (assignment));
|
|
|
|
if (gimple_store_p (assignment)
|
|
&& !gimple_clobber_p (assignment))
|
|
{
|
|
ref->start = gimple_assign_lhs (assignment);
|
|
*ref_is_store = true;
|
|
}
|
|
else if (gimple_assign_load_p (assignment))
|
|
{
|
|
ref->start = gimple_assign_rhs1 (assignment);
|
|
*ref_is_store = false;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
ref->access_size = int_size_in_bytes (TREE_TYPE (ref->start));
|
|
return true;
|
|
}
|
|
|
|
/* Return the memory references contained in a gimple statement
|
|
representing a builtin call that has to do with memory access. */
|
|
|
|
static bool
|
|
get_mem_refs_of_builtin_call (const gcall *call,
|
|
asan_mem_ref *src0,
|
|
tree *src0_len,
|
|
bool *src0_is_store,
|
|
asan_mem_ref *src1,
|
|
tree *src1_len,
|
|
bool *src1_is_store,
|
|
asan_mem_ref *dst,
|
|
tree *dst_len,
|
|
bool *dst_is_store,
|
|
bool *dest_is_deref,
|
|
bool *intercepted_p)
|
|
{
|
|
gcc_checking_assert (gimple_call_builtin_p (call, BUILT_IN_NORMAL));
|
|
|
|
tree callee = gimple_call_fndecl (call);
|
|
tree source0 = NULL_TREE, source1 = NULL_TREE,
|
|
dest = NULL_TREE, len = NULL_TREE;
|
|
bool is_store = true, got_reference_p = false;
|
|
HOST_WIDE_INT access_size = 1;
|
|
|
|
*intercepted_p = asan_intercepted_p ((DECL_FUNCTION_CODE (callee)));
|
|
|
|
switch (DECL_FUNCTION_CODE (callee))
|
|
{
|
|
/* (s, s, n) style memops. */
|
|
case BUILT_IN_BCMP:
|
|
case BUILT_IN_MEMCMP:
|
|
source0 = gimple_call_arg (call, 0);
|
|
source1 = gimple_call_arg (call, 1);
|
|
len = gimple_call_arg (call, 2);
|
|
break;
|
|
|
|
/* (src, dest, n) style memops. */
|
|
case BUILT_IN_BCOPY:
|
|
source0 = gimple_call_arg (call, 0);
|
|
dest = gimple_call_arg (call, 1);
|
|
len = gimple_call_arg (call, 2);
|
|
break;
|
|
|
|
/* (dest, src, n) style memops. */
|
|
case BUILT_IN_MEMCPY:
|
|
case BUILT_IN_MEMCPY_CHK:
|
|
case BUILT_IN_MEMMOVE:
|
|
case BUILT_IN_MEMMOVE_CHK:
|
|
case BUILT_IN_MEMPCPY:
|
|
case BUILT_IN_MEMPCPY_CHK:
|
|
dest = gimple_call_arg (call, 0);
|
|
source0 = gimple_call_arg (call, 1);
|
|
len = gimple_call_arg (call, 2);
|
|
break;
|
|
|
|
/* (dest, n) style memops. */
|
|
case BUILT_IN_BZERO:
|
|
dest = gimple_call_arg (call, 0);
|
|
len = gimple_call_arg (call, 1);
|
|
break;
|
|
|
|
/* (dest, x, n) style memops*/
|
|
case BUILT_IN_MEMSET:
|
|
case BUILT_IN_MEMSET_CHK:
|
|
dest = gimple_call_arg (call, 0);
|
|
len = gimple_call_arg (call, 2);
|
|
break;
|
|
|
|
case BUILT_IN_STRLEN:
|
|
source0 = gimple_call_arg (call, 0);
|
|
len = gimple_call_lhs (call);
|
|
break;
|
|
|
|
/* And now the __atomic* and __sync builtins.
|
|
These are handled differently from the classical memory memory
|
|
access builtins above. */
|
|
|
|
case BUILT_IN_ATOMIC_LOAD_1:
|
|
is_store = false;
|
|
/* FALLTHRU */
|
|
case BUILT_IN_SYNC_FETCH_AND_ADD_1:
|
|
case BUILT_IN_SYNC_FETCH_AND_SUB_1:
|
|
case BUILT_IN_SYNC_FETCH_AND_OR_1:
|
|
case BUILT_IN_SYNC_FETCH_AND_AND_1:
|
|
case BUILT_IN_SYNC_FETCH_AND_XOR_1:
|
|
case BUILT_IN_SYNC_FETCH_AND_NAND_1:
|
|
case BUILT_IN_SYNC_ADD_AND_FETCH_1:
|
|
case BUILT_IN_SYNC_SUB_AND_FETCH_1:
|
|
case BUILT_IN_SYNC_OR_AND_FETCH_1:
|
|
case BUILT_IN_SYNC_AND_AND_FETCH_1:
|
|
case BUILT_IN_SYNC_XOR_AND_FETCH_1:
|
|
case BUILT_IN_SYNC_NAND_AND_FETCH_1:
|
|
case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_1:
|
|
case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_1:
|
|
case BUILT_IN_SYNC_LOCK_TEST_AND_SET_1:
|
|
case BUILT_IN_SYNC_LOCK_RELEASE_1:
|
|
case BUILT_IN_ATOMIC_EXCHANGE_1:
|
|
case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_1:
|
|
case BUILT_IN_ATOMIC_STORE_1:
|
|
case BUILT_IN_ATOMIC_ADD_FETCH_1:
|
|
case BUILT_IN_ATOMIC_SUB_FETCH_1:
|
|
case BUILT_IN_ATOMIC_AND_FETCH_1:
|
|
case BUILT_IN_ATOMIC_NAND_FETCH_1:
|
|
case BUILT_IN_ATOMIC_XOR_FETCH_1:
|
|
case BUILT_IN_ATOMIC_OR_FETCH_1:
|
|
case BUILT_IN_ATOMIC_FETCH_ADD_1:
|
|
case BUILT_IN_ATOMIC_FETCH_SUB_1:
|
|
case BUILT_IN_ATOMIC_FETCH_AND_1:
|
|
case BUILT_IN_ATOMIC_FETCH_NAND_1:
|
|
case BUILT_IN_ATOMIC_FETCH_XOR_1:
|
|
case BUILT_IN_ATOMIC_FETCH_OR_1:
|
|
access_size = 1;
|
|
goto do_atomic;
|
|
|
|
case BUILT_IN_ATOMIC_LOAD_2:
|
|
is_store = false;
|
|
/* FALLTHRU */
|
|
case BUILT_IN_SYNC_FETCH_AND_ADD_2:
|
|
case BUILT_IN_SYNC_FETCH_AND_SUB_2:
|
|
case BUILT_IN_SYNC_FETCH_AND_OR_2:
|
|
case BUILT_IN_SYNC_FETCH_AND_AND_2:
|
|
case BUILT_IN_SYNC_FETCH_AND_XOR_2:
|
|
case BUILT_IN_SYNC_FETCH_AND_NAND_2:
|
|
case BUILT_IN_SYNC_ADD_AND_FETCH_2:
|
|
case BUILT_IN_SYNC_SUB_AND_FETCH_2:
|
|
case BUILT_IN_SYNC_OR_AND_FETCH_2:
|
|
case BUILT_IN_SYNC_AND_AND_FETCH_2:
|
|
case BUILT_IN_SYNC_XOR_AND_FETCH_2:
|
|
case BUILT_IN_SYNC_NAND_AND_FETCH_2:
|
|
case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_2:
|
|
case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_2:
|
|
case BUILT_IN_SYNC_LOCK_TEST_AND_SET_2:
|
|
case BUILT_IN_SYNC_LOCK_RELEASE_2:
|
|
case BUILT_IN_ATOMIC_EXCHANGE_2:
|
|
case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_2:
|
|
case BUILT_IN_ATOMIC_STORE_2:
|
|
case BUILT_IN_ATOMIC_ADD_FETCH_2:
|
|
case BUILT_IN_ATOMIC_SUB_FETCH_2:
|
|
case BUILT_IN_ATOMIC_AND_FETCH_2:
|
|
case BUILT_IN_ATOMIC_NAND_FETCH_2:
|
|
case BUILT_IN_ATOMIC_XOR_FETCH_2:
|
|
case BUILT_IN_ATOMIC_OR_FETCH_2:
|
|
case BUILT_IN_ATOMIC_FETCH_ADD_2:
|
|
case BUILT_IN_ATOMIC_FETCH_SUB_2:
|
|
case BUILT_IN_ATOMIC_FETCH_AND_2:
|
|
case BUILT_IN_ATOMIC_FETCH_NAND_2:
|
|
case BUILT_IN_ATOMIC_FETCH_XOR_2:
|
|
case BUILT_IN_ATOMIC_FETCH_OR_2:
|
|
access_size = 2;
|
|
goto do_atomic;
|
|
|
|
case BUILT_IN_ATOMIC_LOAD_4:
|
|
is_store = false;
|
|
/* FALLTHRU */
|
|
case BUILT_IN_SYNC_FETCH_AND_ADD_4:
|
|
case BUILT_IN_SYNC_FETCH_AND_SUB_4:
|
|
case BUILT_IN_SYNC_FETCH_AND_OR_4:
|
|
case BUILT_IN_SYNC_FETCH_AND_AND_4:
|
|
case BUILT_IN_SYNC_FETCH_AND_XOR_4:
|
|
case BUILT_IN_SYNC_FETCH_AND_NAND_4:
|
|
case BUILT_IN_SYNC_ADD_AND_FETCH_4:
|
|
case BUILT_IN_SYNC_SUB_AND_FETCH_4:
|
|
case BUILT_IN_SYNC_OR_AND_FETCH_4:
|
|
case BUILT_IN_SYNC_AND_AND_FETCH_4:
|
|
case BUILT_IN_SYNC_XOR_AND_FETCH_4:
|
|
case BUILT_IN_SYNC_NAND_AND_FETCH_4:
|
|
case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_4:
|
|
case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_4:
|
|
case BUILT_IN_SYNC_LOCK_TEST_AND_SET_4:
|
|
case BUILT_IN_SYNC_LOCK_RELEASE_4:
|
|
case BUILT_IN_ATOMIC_EXCHANGE_4:
|
|
case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_4:
|
|
case BUILT_IN_ATOMIC_STORE_4:
|
|
case BUILT_IN_ATOMIC_ADD_FETCH_4:
|
|
case BUILT_IN_ATOMIC_SUB_FETCH_4:
|
|
case BUILT_IN_ATOMIC_AND_FETCH_4:
|
|
case BUILT_IN_ATOMIC_NAND_FETCH_4:
|
|
case BUILT_IN_ATOMIC_XOR_FETCH_4:
|
|
case BUILT_IN_ATOMIC_OR_FETCH_4:
|
|
case BUILT_IN_ATOMIC_FETCH_ADD_4:
|
|
case BUILT_IN_ATOMIC_FETCH_SUB_4:
|
|
case BUILT_IN_ATOMIC_FETCH_AND_4:
|
|
case BUILT_IN_ATOMIC_FETCH_NAND_4:
|
|
case BUILT_IN_ATOMIC_FETCH_XOR_4:
|
|
case BUILT_IN_ATOMIC_FETCH_OR_4:
|
|
access_size = 4;
|
|
goto do_atomic;
|
|
|
|
case BUILT_IN_ATOMIC_LOAD_8:
|
|
is_store = false;
|
|
/* FALLTHRU */
|
|
case BUILT_IN_SYNC_FETCH_AND_ADD_8:
|
|
case BUILT_IN_SYNC_FETCH_AND_SUB_8:
|
|
case BUILT_IN_SYNC_FETCH_AND_OR_8:
|
|
case BUILT_IN_SYNC_FETCH_AND_AND_8:
|
|
case BUILT_IN_SYNC_FETCH_AND_XOR_8:
|
|
case BUILT_IN_SYNC_FETCH_AND_NAND_8:
|
|
case BUILT_IN_SYNC_ADD_AND_FETCH_8:
|
|
case BUILT_IN_SYNC_SUB_AND_FETCH_8:
|
|
case BUILT_IN_SYNC_OR_AND_FETCH_8:
|
|
case BUILT_IN_SYNC_AND_AND_FETCH_8:
|
|
case BUILT_IN_SYNC_XOR_AND_FETCH_8:
|
|
case BUILT_IN_SYNC_NAND_AND_FETCH_8:
|
|
case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_8:
|
|
case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_8:
|
|
case BUILT_IN_SYNC_LOCK_TEST_AND_SET_8:
|
|
case BUILT_IN_SYNC_LOCK_RELEASE_8:
|
|
case BUILT_IN_ATOMIC_EXCHANGE_8:
|
|
case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_8:
|
|
case BUILT_IN_ATOMIC_STORE_8:
|
|
case BUILT_IN_ATOMIC_ADD_FETCH_8:
|
|
case BUILT_IN_ATOMIC_SUB_FETCH_8:
|
|
case BUILT_IN_ATOMIC_AND_FETCH_8:
|
|
case BUILT_IN_ATOMIC_NAND_FETCH_8:
|
|
case BUILT_IN_ATOMIC_XOR_FETCH_8:
|
|
case BUILT_IN_ATOMIC_OR_FETCH_8:
|
|
case BUILT_IN_ATOMIC_FETCH_ADD_8:
|
|
case BUILT_IN_ATOMIC_FETCH_SUB_8:
|
|
case BUILT_IN_ATOMIC_FETCH_AND_8:
|
|
case BUILT_IN_ATOMIC_FETCH_NAND_8:
|
|
case BUILT_IN_ATOMIC_FETCH_XOR_8:
|
|
case BUILT_IN_ATOMIC_FETCH_OR_8:
|
|
access_size = 8;
|
|
goto do_atomic;
|
|
|
|
case BUILT_IN_ATOMIC_LOAD_16:
|
|
is_store = false;
|
|
/* FALLTHRU */
|
|
case BUILT_IN_SYNC_FETCH_AND_ADD_16:
|
|
case BUILT_IN_SYNC_FETCH_AND_SUB_16:
|
|
case BUILT_IN_SYNC_FETCH_AND_OR_16:
|
|
case BUILT_IN_SYNC_FETCH_AND_AND_16:
|
|
case BUILT_IN_SYNC_FETCH_AND_XOR_16:
|
|
case BUILT_IN_SYNC_FETCH_AND_NAND_16:
|
|
case BUILT_IN_SYNC_ADD_AND_FETCH_16:
|
|
case BUILT_IN_SYNC_SUB_AND_FETCH_16:
|
|
case BUILT_IN_SYNC_OR_AND_FETCH_16:
|
|
case BUILT_IN_SYNC_AND_AND_FETCH_16:
|
|
case BUILT_IN_SYNC_XOR_AND_FETCH_16:
|
|
case BUILT_IN_SYNC_NAND_AND_FETCH_16:
|
|
case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_16:
|
|
case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_16:
|
|
case BUILT_IN_SYNC_LOCK_TEST_AND_SET_16:
|
|
case BUILT_IN_SYNC_LOCK_RELEASE_16:
|
|
case BUILT_IN_ATOMIC_EXCHANGE_16:
|
|
case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_16:
|
|
case BUILT_IN_ATOMIC_STORE_16:
|
|
case BUILT_IN_ATOMIC_ADD_FETCH_16:
|
|
case BUILT_IN_ATOMIC_SUB_FETCH_16:
|
|
case BUILT_IN_ATOMIC_AND_FETCH_16:
|
|
case BUILT_IN_ATOMIC_NAND_FETCH_16:
|
|
case BUILT_IN_ATOMIC_XOR_FETCH_16:
|
|
case BUILT_IN_ATOMIC_OR_FETCH_16:
|
|
case BUILT_IN_ATOMIC_FETCH_ADD_16:
|
|
case BUILT_IN_ATOMIC_FETCH_SUB_16:
|
|
case BUILT_IN_ATOMIC_FETCH_AND_16:
|
|
case BUILT_IN_ATOMIC_FETCH_NAND_16:
|
|
case BUILT_IN_ATOMIC_FETCH_XOR_16:
|
|
case BUILT_IN_ATOMIC_FETCH_OR_16:
|
|
access_size = 16;
|
|
/* FALLTHRU */
|
|
do_atomic:
|
|
{
|
|
dest = gimple_call_arg (call, 0);
|
|
/* DEST represents the address of a memory location.
|
|
instrument_derefs wants the memory location, so lets
|
|
dereference the address DEST before handing it to
|
|
instrument_derefs. */
|
|
tree type = build_nonstandard_integer_type (access_size
|
|
* BITS_PER_UNIT, 1);
|
|
dest = build2 (MEM_REF, type, dest,
|
|
build_int_cst (build_pointer_type (char_type_node), 0));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
/* The other builtins memory access are not instrumented in this
|
|
function because they either don't have any length parameter,
|
|
or their length parameter is just a limit. */
|
|
break;
|
|
}
|
|
|
|
if (len != NULL_TREE)
|
|
{
|
|
if (source0 != NULL_TREE)
|
|
{
|
|
src0->start = source0;
|
|
src0->access_size = access_size;
|
|
*src0_len = len;
|
|
*src0_is_store = false;
|
|
}
|
|
|
|
if (source1 != NULL_TREE)
|
|
{
|
|
src1->start = source1;
|
|
src1->access_size = access_size;
|
|
*src1_len = len;
|
|
*src1_is_store = false;
|
|
}
|
|
|
|
if (dest != NULL_TREE)
|
|
{
|
|
dst->start = dest;
|
|
dst->access_size = access_size;
|
|
*dst_len = len;
|
|
*dst_is_store = true;
|
|
}
|
|
|
|
got_reference_p = true;
|
|
}
|
|
else if (dest)
|
|
{
|
|
dst->start = dest;
|
|
dst->access_size = access_size;
|
|
*dst_len = NULL_TREE;
|
|
*dst_is_store = is_store;
|
|
*dest_is_deref = true;
|
|
got_reference_p = true;
|
|
}
|
|
|
|
return got_reference_p;
|
|
}
|
|
|
|
/* Return true iff a given gimple statement has been instrumented.
|
|
Note that the statement is "defined" by the memory references it
|
|
contains. */
|
|
|
|
static bool
|
|
has_stmt_been_instrumented_p (gimple *stmt)
|
|
{
|
|
if (gimple_assign_single_p (stmt))
|
|
{
|
|
bool r_is_store;
|
|
asan_mem_ref r;
|
|
asan_mem_ref_init (&r, NULL, 1);
|
|
|
|
if (get_mem_ref_of_assignment (as_a <gassign *> (stmt), &r,
|
|
&r_is_store))
|
|
return has_mem_ref_been_instrumented (&r);
|
|
}
|
|
else if (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
|
|
{
|
|
asan_mem_ref src0, src1, dest;
|
|
asan_mem_ref_init (&src0, NULL, 1);
|
|
asan_mem_ref_init (&src1, NULL, 1);
|
|
asan_mem_ref_init (&dest, NULL, 1);
|
|
|
|
tree src0_len = NULL_TREE, src1_len = NULL_TREE, dest_len = NULL_TREE;
|
|
bool src0_is_store = false, src1_is_store = false,
|
|
dest_is_store = false, dest_is_deref = false, intercepted_p = true;
|
|
if (get_mem_refs_of_builtin_call (as_a <gcall *> (stmt),
|
|
&src0, &src0_len, &src0_is_store,
|
|
&src1, &src1_len, &src1_is_store,
|
|
&dest, &dest_len, &dest_is_store,
|
|
&dest_is_deref, &intercepted_p))
|
|
{
|
|
if (src0.start != NULL_TREE
|
|
&& !has_mem_ref_been_instrumented (&src0, src0_len))
|
|
return false;
|
|
|
|
if (src1.start != NULL_TREE
|
|
&& !has_mem_ref_been_instrumented (&src1, src1_len))
|
|
return false;
|
|
|
|
if (dest.start != NULL_TREE
|
|
&& !has_mem_ref_been_instrumented (&dest, dest_len))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
else if (is_gimple_call (stmt) && gimple_store_p (stmt))
|
|
{
|
|
asan_mem_ref r;
|
|
asan_mem_ref_init (&r, NULL, 1);
|
|
|
|
r.start = gimple_call_lhs (stmt);
|
|
r.access_size = int_size_in_bytes (TREE_TYPE (r.start));
|
|
return has_mem_ref_been_instrumented (&r);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Insert a memory reference into the hash table. */
|
|
|
|
static void
|
|
update_mem_ref_hash_table (tree ref, HOST_WIDE_INT access_size)
|
|
{
|
|
hash_table<asan_mem_ref_hasher> *ht = get_mem_ref_hash_table ();
|
|
|
|
asan_mem_ref r;
|
|
asan_mem_ref_init (&r, ref, access_size);
|
|
|
|
asan_mem_ref **slot = ht->find_slot (&r, INSERT);
|
|
if (*slot == NULL || (*slot)->access_size < access_size)
|
|
*slot = asan_mem_ref_new (ref, access_size);
|
|
}
|
|
|
|
/* Initialize shadow_ptr_types array. */
|
|
|
|
static void
|
|
asan_init_shadow_ptr_types (void)
|
|
{
|
|
asan_shadow_set = new_alias_set ();
|
|
tree types[3] = { signed_char_type_node, short_integer_type_node,
|
|
integer_type_node };
|
|
|
|
for (unsigned i = 0; i < 3; i++)
|
|
{
|
|
shadow_ptr_types[i] = build_distinct_type_copy (types[i]);
|
|
TYPE_ALIAS_SET (shadow_ptr_types[i]) = asan_shadow_set;
|
|
shadow_ptr_types[i] = build_pointer_type (shadow_ptr_types[i]);
|
|
}
|
|
|
|
initialize_sanitizer_builtins ();
|
|
}
|
|
|
|
/* Create ADDR_EXPR of STRING_CST with the PP pretty printer text. */
|
|
|
|
static tree
|
|
asan_pp_string (pretty_printer *pp)
|
|
{
|
|
const char *buf = pp_formatted_text (pp);
|
|
size_t len = strlen (buf);
|
|
tree ret = build_string (len + 1, buf);
|
|
TREE_TYPE (ret)
|
|
= build_array_type (TREE_TYPE (shadow_ptr_types[0]),
|
|
build_index_type (size_int (len)));
|
|
TREE_READONLY (ret) = 1;
|
|
TREE_STATIC (ret) = 1;
|
|
return build1 (ADDR_EXPR, shadow_ptr_types[0], ret);
|
|
}
|
|
|
|
/* Return a CONST_INT representing 4 subsequent shadow memory bytes. */
|
|
|
|
static rtx
|
|
asan_shadow_cst (unsigned char shadow_bytes[4])
|
|
{
|
|
int i;
|
|
unsigned HOST_WIDE_INT val = 0;
|
|
gcc_assert (WORDS_BIG_ENDIAN == BYTES_BIG_ENDIAN);
|
|
for (i = 0; i < 4; i++)
|
|
val |= (unsigned HOST_WIDE_INT) shadow_bytes[BYTES_BIG_ENDIAN ? 3 - i : i]
|
|
<< (BITS_PER_UNIT * i);
|
|
return gen_int_mode (val, SImode);
|
|
}
|
|
|
|
/* Clear shadow memory at SHADOW_MEM, LEN bytes. Can't call a library call here
|
|
though. */
|
|
|
|
static void
|
|
asan_clear_shadow (rtx shadow_mem, HOST_WIDE_INT len)
|
|
{
|
|
rtx_insn *insn, *insns, *jump;
|
|
rtx_code_label *top_label;
|
|
rtx end, addr, tmp;
|
|
|
|
start_sequence ();
|
|
clear_storage (shadow_mem, GEN_INT (len), BLOCK_OP_NORMAL);
|
|
insns = get_insns ();
|
|
end_sequence ();
|
|
for (insn = insns; insn; insn = NEXT_INSN (insn))
|
|
if (CALL_P (insn))
|
|
break;
|
|
if (insn == NULL_RTX)
|
|
{
|
|
emit_insn (insns);
|
|
return;
|
|
}
|
|
|
|
gcc_assert ((len & 3) == 0);
|
|
top_label = gen_label_rtx ();
|
|
addr = copy_to_mode_reg (Pmode, XEXP (shadow_mem, 0));
|
|
shadow_mem = adjust_automodify_address (shadow_mem, SImode, addr, 0);
|
|
end = force_reg (Pmode, plus_constant (Pmode, addr, len));
|
|
emit_label (top_label);
|
|
|
|
emit_move_insn (shadow_mem, const0_rtx);
|
|
tmp = expand_simple_binop (Pmode, PLUS, addr, gen_int_mode (4, Pmode), addr,
|
|
true, OPTAB_LIB_WIDEN);
|
|
if (tmp != addr)
|
|
emit_move_insn (addr, tmp);
|
|
emit_cmp_and_jump_insns (addr, end, LT, NULL_RTX, Pmode, true, top_label);
|
|
jump = get_last_insn ();
|
|
gcc_assert (JUMP_P (jump));
|
|
add_int_reg_note (jump, REG_BR_PROB, REG_BR_PROB_BASE * 80 / 100);
|
|
}
|
|
|
|
void
|
|
asan_function_start (void)
|
|
{
|
|
section *fnsec = function_section (current_function_decl);
|
|
switch_to_section (fnsec);
|
|
ASM_OUTPUT_DEBUG_LABEL (asm_out_file, "LASANPC",
|
|
current_function_funcdef_no);
|
|
}
|
|
|
|
/* Return number of shadow bytes that are occupied by a local variable
|
|
of SIZE bytes. */
|
|
|
|
static unsigned HOST_WIDE_INT
|
|
shadow_mem_size (unsigned HOST_WIDE_INT size)
|
|
{
|
|
return ROUND_UP (size, ASAN_SHADOW_GRANULARITY) / ASAN_SHADOW_GRANULARITY;
|
|
}
|
|
|
|
/* Insert code to protect stack vars. The prologue sequence should be emitted
|
|
directly, epilogue sequence returned. BASE is the register holding the
|
|
stack base, against which OFFSETS array offsets are relative to, OFFSETS
|
|
array contains pairs of offsets in reverse order, always the end offset
|
|
of some gap that needs protection followed by starting offset,
|
|
and DECLS is an array of representative decls for each var partition.
|
|
LENGTH is the length of the OFFSETS array, DECLS array is LENGTH / 2 - 1
|
|
elements long (OFFSETS include gap before the first variable as well
|
|
as gaps after each stack variable). PBASE is, if non-NULL, some pseudo
|
|
register which stack vars DECL_RTLs are based on. Either BASE should be
|
|
assigned to PBASE, when not doing use after return protection, or
|
|
corresponding address based on __asan_stack_malloc* return value. */
|
|
|
|
rtx_insn *
|
|
asan_emit_stack_protection (rtx base, rtx pbase, unsigned int alignb,
|
|
HOST_WIDE_INT *offsets, tree *decls, int length)
|
|
{
|
|
rtx shadow_base, shadow_mem, ret, mem, orig_base;
|
|
rtx_code_label *lab;
|
|
rtx_insn *insns;
|
|
char buf[32];
|
|
unsigned char shadow_bytes[4];
|
|
HOST_WIDE_INT base_offset = offsets[length - 1];
|
|
HOST_WIDE_INT base_align_bias = 0, offset, prev_offset;
|
|
HOST_WIDE_INT asan_frame_size = offsets[0] - base_offset;
|
|
HOST_WIDE_INT last_offset;
|
|
int l;
|
|
unsigned char cur_shadow_byte = ASAN_STACK_MAGIC_LEFT;
|
|
tree str_cst, decl, id;
|
|
int use_after_return_class = -1;
|
|
|
|
if (shadow_ptr_types[0] == NULL_TREE)
|
|
asan_init_shadow_ptr_types ();
|
|
|
|
/* First of all, prepare the description string. */
|
|
pretty_printer asan_pp;
|
|
|
|
pp_decimal_int (&asan_pp, length / 2 - 1);
|
|
pp_space (&asan_pp);
|
|
for (l = length - 2; l; l -= 2)
|
|
{
|
|
tree decl = decls[l / 2 - 1];
|
|
pp_wide_integer (&asan_pp, offsets[l] - base_offset);
|
|
pp_space (&asan_pp);
|
|
pp_wide_integer (&asan_pp, offsets[l - 1] - offsets[l]);
|
|
pp_space (&asan_pp);
|
|
if (DECL_P (decl) && DECL_NAME (decl))
|
|
{
|
|
pp_decimal_int (&asan_pp, IDENTIFIER_LENGTH (DECL_NAME (decl)));
|
|
pp_space (&asan_pp);
|
|
pp_tree_identifier (&asan_pp, DECL_NAME (decl));
|
|
}
|
|
else
|
|
pp_string (&asan_pp, "9 <unknown>");
|
|
pp_space (&asan_pp);
|
|
}
|
|
str_cst = asan_pp_string (&asan_pp);
|
|
|
|
/* Emit the prologue sequence. */
|
|
if (asan_frame_size > 32 && asan_frame_size <= 65536 && pbase
|
|
&& ASAN_USE_AFTER_RETURN)
|
|
{
|
|
use_after_return_class = floor_log2 (asan_frame_size - 1) - 5;
|
|
/* __asan_stack_malloc_N guarantees alignment
|
|
N < 6 ? (64 << N) : 4096 bytes. */
|
|
if (alignb > (use_after_return_class < 6
|
|
? (64U << use_after_return_class) : 4096U))
|
|
use_after_return_class = -1;
|
|
else if (alignb > ASAN_RED_ZONE_SIZE && (asan_frame_size & (alignb - 1)))
|
|
base_align_bias = ((asan_frame_size + alignb - 1)
|
|
& ~(alignb - HOST_WIDE_INT_1)) - asan_frame_size;
|
|
}
|
|
/* Align base if target is STRICT_ALIGNMENT. */
|
|
if (STRICT_ALIGNMENT)
|
|
base = expand_binop (Pmode, and_optab, base,
|
|
gen_int_mode (-((GET_MODE_ALIGNMENT (SImode)
|
|
<< ASAN_SHADOW_SHIFT)
|
|
/ BITS_PER_UNIT), Pmode), NULL_RTX,
|
|
1, OPTAB_DIRECT);
|
|
|
|
if (use_after_return_class == -1 && pbase)
|
|
emit_move_insn (pbase, base);
|
|
|
|
base = expand_binop (Pmode, add_optab, base,
|
|
gen_int_mode (base_offset - base_align_bias, Pmode),
|
|
NULL_RTX, 1, OPTAB_DIRECT);
|
|
orig_base = NULL_RTX;
|
|
if (use_after_return_class != -1)
|
|
{
|
|
if (asan_detect_stack_use_after_return == NULL_TREE)
|
|
{
|
|
id = get_identifier ("__asan_option_detect_stack_use_after_return");
|
|
decl = build_decl (BUILTINS_LOCATION, VAR_DECL, id,
|
|
integer_type_node);
|
|
SET_DECL_ASSEMBLER_NAME (decl, id);
|
|
TREE_ADDRESSABLE (decl) = 1;
|
|
DECL_ARTIFICIAL (decl) = 1;
|
|
DECL_IGNORED_P (decl) = 1;
|
|
DECL_EXTERNAL (decl) = 1;
|
|
TREE_STATIC (decl) = 1;
|
|
TREE_PUBLIC (decl) = 1;
|
|
TREE_USED (decl) = 1;
|
|
asan_detect_stack_use_after_return = decl;
|
|
}
|
|
orig_base = gen_reg_rtx (Pmode);
|
|
emit_move_insn (orig_base, base);
|
|
ret = expand_normal (asan_detect_stack_use_after_return);
|
|
lab = gen_label_rtx ();
|
|
int very_likely = REG_BR_PROB_BASE - (REG_BR_PROB_BASE / 2000 - 1);
|
|
emit_cmp_and_jump_insns (ret, const0_rtx, EQ, NULL_RTX,
|
|
VOIDmode, 0, lab, very_likely);
|
|
snprintf (buf, sizeof buf, "__asan_stack_malloc_%d",
|
|
use_after_return_class);
|
|
ret = init_one_libfunc (buf);
|
|
ret = emit_library_call_value (ret, NULL_RTX, LCT_NORMAL, ptr_mode, 1,
|
|
GEN_INT (asan_frame_size
|
|
+ base_align_bias),
|
|
TYPE_MODE (pointer_sized_int_node));
|
|
/* __asan_stack_malloc_[n] returns a pointer to fake stack if succeeded
|
|
and NULL otherwise. Check RET value is NULL here and jump over the
|
|
BASE reassignment in this case. Otherwise, reassign BASE to RET. */
|
|
int very_unlikely = REG_BR_PROB_BASE / 2000 - 1;
|
|
emit_cmp_and_jump_insns (ret, const0_rtx, EQ, NULL_RTX,
|
|
VOIDmode, 0, lab, very_unlikely);
|
|
ret = convert_memory_address (Pmode, ret);
|
|
emit_move_insn (base, ret);
|
|
emit_label (lab);
|
|
emit_move_insn (pbase, expand_binop (Pmode, add_optab, base,
|
|
gen_int_mode (base_align_bias
|
|
- base_offset, Pmode),
|
|
NULL_RTX, 1, OPTAB_DIRECT));
|
|
}
|
|
mem = gen_rtx_MEM (ptr_mode, base);
|
|
mem = adjust_address (mem, VOIDmode, base_align_bias);
|
|
emit_move_insn (mem, gen_int_mode (ASAN_STACK_FRAME_MAGIC, ptr_mode));
|
|
mem = adjust_address (mem, VOIDmode, GET_MODE_SIZE (ptr_mode));
|
|
emit_move_insn (mem, expand_normal (str_cst));
|
|
mem = adjust_address (mem, VOIDmode, GET_MODE_SIZE (ptr_mode));
|
|
ASM_GENERATE_INTERNAL_LABEL (buf, "LASANPC", current_function_funcdef_no);
|
|
id = get_identifier (buf);
|
|
decl = build_decl (DECL_SOURCE_LOCATION (current_function_decl),
|
|
VAR_DECL, id, char_type_node);
|
|
SET_DECL_ASSEMBLER_NAME (decl, id);
|
|
TREE_ADDRESSABLE (decl) = 1;
|
|
TREE_READONLY (decl) = 1;
|
|
DECL_ARTIFICIAL (decl) = 1;
|
|
DECL_IGNORED_P (decl) = 1;
|
|
TREE_STATIC (decl) = 1;
|
|
TREE_PUBLIC (decl) = 0;
|
|
TREE_USED (decl) = 1;
|
|
DECL_INITIAL (decl) = decl;
|
|
TREE_ASM_WRITTEN (decl) = 1;
|
|
TREE_ASM_WRITTEN (id) = 1;
|
|
emit_move_insn (mem, expand_normal (build_fold_addr_expr (decl)));
|
|
shadow_base = expand_binop (Pmode, lshr_optab, base,
|
|
GEN_INT (ASAN_SHADOW_SHIFT),
|
|
NULL_RTX, 1, OPTAB_DIRECT);
|
|
shadow_base
|
|
= plus_constant (Pmode, shadow_base,
|
|
asan_shadow_offset ()
|
|
+ (base_align_bias >> ASAN_SHADOW_SHIFT));
|
|
gcc_assert (asan_shadow_set != -1
|
|
&& (ASAN_RED_ZONE_SIZE >> ASAN_SHADOW_SHIFT) == 4);
|
|
shadow_mem = gen_rtx_MEM (SImode, shadow_base);
|
|
set_mem_alias_set (shadow_mem, asan_shadow_set);
|
|
if (STRICT_ALIGNMENT)
|
|
set_mem_align (shadow_mem, (GET_MODE_ALIGNMENT (SImode)));
|
|
prev_offset = base_offset;
|
|
for (l = length; l; l -= 2)
|
|
{
|
|
if (l == 2)
|
|
cur_shadow_byte = ASAN_STACK_MAGIC_RIGHT;
|
|
offset = offsets[l - 1];
|
|
if ((offset - base_offset) & (ASAN_RED_ZONE_SIZE - 1))
|
|
{
|
|
int i;
|
|
HOST_WIDE_INT aoff
|
|
= base_offset + ((offset - base_offset)
|
|
& ~(ASAN_RED_ZONE_SIZE - HOST_WIDE_INT_1));
|
|
shadow_mem = adjust_address (shadow_mem, VOIDmode,
|
|
(aoff - prev_offset)
|
|
>> ASAN_SHADOW_SHIFT);
|
|
prev_offset = aoff;
|
|
for (i = 0; i < 4; i++, aoff += ASAN_SHADOW_GRANULARITY)
|
|
if (aoff < offset)
|
|
{
|
|
if (aoff < offset - (HOST_WIDE_INT)ASAN_SHADOW_GRANULARITY + 1)
|
|
shadow_bytes[i] = 0;
|
|
else
|
|
shadow_bytes[i] = offset - aoff;
|
|
}
|
|
else
|
|
shadow_bytes[i] = ASAN_STACK_MAGIC_MIDDLE;
|
|
emit_move_insn (shadow_mem, asan_shadow_cst (shadow_bytes));
|
|
offset = aoff;
|
|
}
|
|
while (offset <= offsets[l - 2] - ASAN_RED_ZONE_SIZE)
|
|
{
|
|
shadow_mem = adjust_address (shadow_mem, VOIDmode,
|
|
(offset - prev_offset)
|
|
>> ASAN_SHADOW_SHIFT);
|
|
prev_offset = offset;
|
|
memset (shadow_bytes, cur_shadow_byte, 4);
|
|
emit_move_insn (shadow_mem, asan_shadow_cst (shadow_bytes));
|
|
offset += ASAN_RED_ZONE_SIZE;
|
|
}
|
|
cur_shadow_byte = ASAN_STACK_MAGIC_MIDDLE;
|
|
}
|
|
do_pending_stack_adjust ();
|
|
|
|
/* Construct epilogue sequence. */
|
|
start_sequence ();
|
|
|
|
lab = NULL;
|
|
if (use_after_return_class != -1)
|
|
{
|
|
rtx_code_label *lab2 = gen_label_rtx ();
|
|
char c = (char) ASAN_STACK_MAGIC_USE_AFTER_RET;
|
|
int very_likely = REG_BR_PROB_BASE - (REG_BR_PROB_BASE / 2000 - 1);
|
|
emit_cmp_and_jump_insns (orig_base, base, EQ, NULL_RTX,
|
|
VOIDmode, 0, lab2, very_likely);
|
|
shadow_mem = gen_rtx_MEM (BLKmode, shadow_base);
|
|
set_mem_alias_set (shadow_mem, asan_shadow_set);
|
|
mem = gen_rtx_MEM (ptr_mode, base);
|
|
mem = adjust_address (mem, VOIDmode, base_align_bias);
|
|
emit_move_insn (mem, gen_int_mode (ASAN_STACK_RETIRED_MAGIC, ptr_mode));
|
|
unsigned HOST_WIDE_INT sz = asan_frame_size >> ASAN_SHADOW_SHIFT;
|
|
if (use_after_return_class < 5
|
|
&& can_store_by_pieces (sz, builtin_memset_read_str, &c,
|
|
BITS_PER_UNIT, true))
|
|
store_by_pieces (shadow_mem, sz, builtin_memset_read_str, &c,
|
|
BITS_PER_UNIT, true, 0);
|
|
else if (use_after_return_class >= 5
|
|
|| !set_storage_via_setmem (shadow_mem,
|
|
GEN_INT (sz),
|
|
gen_int_mode (c, QImode),
|
|
BITS_PER_UNIT, BITS_PER_UNIT,
|
|
-1, sz, sz, sz))
|
|
{
|
|
snprintf (buf, sizeof buf, "__asan_stack_free_%d",
|
|
use_after_return_class);
|
|
ret = init_one_libfunc (buf);
|
|
rtx addr = convert_memory_address (ptr_mode, base);
|
|
rtx orig_addr = convert_memory_address (ptr_mode, orig_base);
|
|
emit_library_call (ret, LCT_NORMAL, ptr_mode, 3, addr, ptr_mode,
|
|
GEN_INT (asan_frame_size + base_align_bias),
|
|
TYPE_MODE (pointer_sized_int_node),
|
|
orig_addr, ptr_mode);
|
|
}
|
|
lab = gen_label_rtx ();
|
|
emit_jump (lab);
|
|
emit_label (lab2);
|
|
}
|
|
|
|
shadow_mem = gen_rtx_MEM (BLKmode, shadow_base);
|
|
set_mem_alias_set (shadow_mem, asan_shadow_set);
|
|
|
|
if (STRICT_ALIGNMENT)
|
|
set_mem_align (shadow_mem, (GET_MODE_ALIGNMENT (SImode)));
|
|
|
|
/* Unpoison shadow memory of a stack at the very end of a function.
|
|
As we're poisoning stack variables at the end of their scope,
|
|
shadow memory must be properly unpoisoned here. The easiest approach
|
|
would be to collect all variables that should not be unpoisoned and
|
|
we unpoison shadow memory of the whole stack except ranges
|
|
occupied by these variables. */
|
|
last_offset = base_offset;
|
|
HOST_WIDE_INT current_offset = last_offset;
|
|
if (length)
|
|
{
|
|
HOST_WIDE_INT var_end_offset = 0;
|
|
HOST_WIDE_INT stack_start = offsets[length - 1];
|
|
gcc_assert (last_offset == stack_start);
|
|
|
|
for (int l = length - 2; l > 0; l -= 2)
|
|
{
|
|
HOST_WIDE_INT var_offset = offsets[l];
|
|
current_offset = var_offset;
|
|
var_end_offset = offsets[l - 1];
|
|
HOST_WIDE_INT rounded_size = ROUND_UP (var_end_offset - var_offset,
|
|
BITS_PER_UNIT);
|
|
|
|
/* Should we unpoison the variable? */
|
|
if (asan_handled_variables != NULL
|
|
&& asan_handled_variables->contains (decl))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
const char *n = (DECL_NAME (decl)
|
|
? IDENTIFIER_POINTER (DECL_NAME (decl))
|
|
: "<unknown>");
|
|
fprintf (dump_file, "Unpoisoning shadow stack for variable: "
|
|
"%s (%" PRId64 "B)\n", n,
|
|
var_end_offset - var_offset);
|
|
}
|
|
|
|
unsigned HOST_WIDE_INT s
|
|
= shadow_mem_size (current_offset - last_offset);
|
|
asan_clear_shadow (shadow_mem, s);
|
|
HOST_WIDE_INT shift
|
|
= shadow_mem_size (current_offset - last_offset + rounded_size);
|
|
shadow_mem = adjust_address (shadow_mem, VOIDmode, shift);
|
|
last_offset = var_offset + rounded_size;
|
|
current_offset = last_offset;
|
|
}
|
|
|
|
}
|
|
|
|
/* Handle last redzone. */
|
|
current_offset = offsets[0];
|
|
asan_clear_shadow (shadow_mem,
|
|
shadow_mem_size (current_offset - last_offset));
|
|
}
|
|
|
|
/* Clean-up set with instrumented stack variables. */
|
|
delete asan_handled_variables;
|
|
asan_handled_variables = NULL;
|
|
delete asan_used_labels;
|
|
asan_used_labels = NULL;
|
|
|
|
do_pending_stack_adjust ();
|
|
if (lab)
|
|
emit_label (lab);
|
|
|
|
insns = get_insns ();
|
|
end_sequence ();
|
|
return insns;
|
|
}
|
|
|
|
/* Return true if DECL, a global var, might be overridden and needs
|
|
therefore a local alias. */
|
|
|
|
static bool
|
|
asan_needs_local_alias (tree decl)
|
|
{
|
|
return DECL_WEAK (decl) || !targetm.binds_local_p (decl);
|
|
}
|
|
|
|
/* Return true if DECL, a global var, is an artificial ODR indicator symbol
|
|
therefore doesn't need protection. */
|
|
|
|
static bool
|
|
is_odr_indicator (tree decl)
|
|
{
|
|
return (DECL_ARTIFICIAL (decl)
|
|
&& lookup_attribute ("asan odr indicator", DECL_ATTRIBUTES (decl)));
|
|
}
|
|
|
|
/* Return true if DECL is a VAR_DECL that should be protected
|
|
by Address Sanitizer, by appending a red zone with protected
|
|
shadow memory after it and aligning it to at least
|
|
ASAN_RED_ZONE_SIZE bytes. */
|
|
|
|
bool
|
|
asan_protect_global (tree decl)
|
|
{
|
|
if (!ASAN_GLOBALS)
|
|
return false;
|
|
|
|
rtx rtl, symbol;
|
|
|
|
if (TREE_CODE (decl) == STRING_CST)
|
|
{
|
|
/* Instrument all STRING_CSTs except those created
|
|
by asan_pp_string here. */
|
|
if (shadow_ptr_types[0] != NULL_TREE
|
|
&& TREE_CODE (TREE_TYPE (decl)) == ARRAY_TYPE
|
|
&& TREE_TYPE (TREE_TYPE (decl)) == TREE_TYPE (shadow_ptr_types[0]))
|
|
return false;
|
|
return true;
|
|
}
|
|
if (!VAR_P (decl)
|
|
/* TLS vars aren't statically protectable. */
|
|
|| DECL_THREAD_LOCAL_P (decl)
|
|
/* Externs will be protected elsewhere. */
|
|
|| DECL_EXTERNAL (decl)
|
|
|| !DECL_RTL_SET_P (decl)
|
|
/* Comdat vars pose an ABI problem, we can't know if
|
|
the var that is selected by the linker will have
|
|
padding or not. */
|
|
|| DECL_ONE_ONLY (decl)
|
|
/* Similarly for common vars. People can use -fno-common.
|
|
Note: Linux kernel is built with -fno-common, so we do instrument
|
|
globals there even if it is C. */
|
|
|| (DECL_COMMON (decl) && TREE_PUBLIC (decl))
|
|
/* Don't protect if using user section, often vars placed
|
|
into user section from multiple TUs are then assumed
|
|
to be an array of such vars, putting padding in there
|
|
breaks this assumption. */
|
|
|| (DECL_SECTION_NAME (decl) != NULL
|
|
&& !symtab_node::get (decl)->implicit_section
|
|
&& !section_sanitized_p (DECL_SECTION_NAME (decl)))
|
|
|| DECL_SIZE (decl) == 0
|
|
|| ASAN_RED_ZONE_SIZE * BITS_PER_UNIT > MAX_OFILE_ALIGNMENT
|
|
|| !valid_constant_size_p (DECL_SIZE_UNIT (decl))
|
|
|| DECL_ALIGN_UNIT (decl) > 2 * ASAN_RED_ZONE_SIZE
|
|
|| TREE_TYPE (decl) == ubsan_get_source_location_type ()
|
|
|| is_odr_indicator (decl))
|
|
return false;
|
|
|
|
rtl = DECL_RTL (decl);
|
|
if (!MEM_P (rtl) || GET_CODE (XEXP (rtl, 0)) != SYMBOL_REF)
|
|
return false;
|
|
symbol = XEXP (rtl, 0);
|
|
|
|
if (CONSTANT_POOL_ADDRESS_P (symbol)
|
|
|| TREE_CONSTANT_POOL_ADDRESS_P (symbol))
|
|
return false;
|
|
|
|
if (lookup_attribute ("weakref", DECL_ATTRIBUTES (decl)))
|
|
return false;
|
|
|
|
#ifndef ASM_OUTPUT_DEF
|
|
if (asan_needs_local_alias (decl))
|
|
return false;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Construct a function tree for __asan_report_{load,store}{1,2,4,8,16,_n}.
|
|
IS_STORE is either 1 (for a store) or 0 (for a load). */
|
|
|
|
static tree
|
|
report_error_func (bool is_store, bool recover_p, HOST_WIDE_INT size_in_bytes,
|
|
int *nargs)
|
|
{
|
|
static enum built_in_function report[2][2][6]
|
|
= { { { BUILT_IN_ASAN_REPORT_LOAD1, BUILT_IN_ASAN_REPORT_LOAD2,
|
|
BUILT_IN_ASAN_REPORT_LOAD4, BUILT_IN_ASAN_REPORT_LOAD8,
|
|
BUILT_IN_ASAN_REPORT_LOAD16, BUILT_IN_ASAN_REPORT_LOAD_N },
|
|
{ BUILT_IN_ASAN_REPORT_STORE1, BUILT_IN_ASAN_REPORT_STORE2,
|
|
BUILT_IN_ASAN_REPORT_STORE4, BUILT_IN_ASAN_REPORT_STORE8,
|
|
BUILT_IN_ASAN_REPORT_STORE16, BUILT_IN_ASAN_REPORT_STORE_N } },
|
|
{ { BUILT_IN_ASAN_REPORT_LOAD1_NOABORT,
|
|
BUILT_IN_ASAN_REPORT_LOAD2_NOABORT,
|
|
BUILT_IN_ASAN_REPORT_LOAD4_NOABORT,
|
|
BUILT_IN_ASAN_REPORT_LOAD8_NOABORT,
|
|
BUILT_IN_ASAN_REPORT_LOAD16_NOABORT,
|
|
BUILT_IN_ASAN_REPORT_LOAD_N_NOABORT },
|
|
{ BUILT_IN_ASAN_REPORT_STORE1_NOABORT,
|
|
BUILT_IN_ASAN_REPORT_STORE2_NOABORT,
|
|
BUILT_IN_ASAN_REPORT_STORE4_NOABORT,
|
|
BUILT_IN_ASAN_REPORT_STORE8_NOABORT,
|
|
BUILT_IN_ASAN_REPORT_STORE16_NOABORT,
|
|
BUILT_IN_ASAN_REPORT_STORE_N_NOABORT } } };
|
|
if (size_in_bytes == -1)
|
|
{
|
|
*nargs = 2;
|
|
return builtin_decl_implicit (report[recover_p][is_store][5]);
|
|
}
|
|
*nargs = 1;
|
|
int size_log2 = exact_log2 (size_in_bytes);
|
|
return builtin_decl_implicit (report[recover_p][is_store][size_log2]);
|
|
}
|
|
|
|
/* Construct a function tree for __asan_{load,store}{1,2,4,8,16,_n}.
|
|
IS_STORE is either 1 (for a store) or 0 (for a load). */
|
|
|
|
static tree
|
|
check_func (bool is_store, bool recover_p, HOST_WIDE_INT size_in_bytes,
|
|
int *nargs)
|
|
{
|
|
static enum built_in_function check[2][2][6]
|
|
= { { { BUILT_IN_ASAN_LOAD1, BUILT_IN_ASAN_LOAD2,
|
|
BUILT_IN_ASAN_LOAD4, BUILT_IN_ASAN_LOAD8,
|
|
BUILT_IN_ASAN_LOAD16, BUILT_IN_ASAN_LOADN },
|
|
{ BUILT_IN_ASAN_STORE1, BUILT_IN_ASAN_STORE2,
|
|
BUILT_IN_ASAN_STORE4, BUILT_IN_ASAN_STORE8,
|
|
BUILT_IN_ASAN_STORE16, BUILT_IN_ASAN_STOREN } },
|
|
{ { BUILT_IN_ASAN_LOAD1_NOABORT,
|
|
BUILT_IN_ASAN_LOAD2_NOABORT,
|
|
BUILT_IN_ASAN_LOAD4_NOABORT,
|
|
BUILT_IN_ASAN_LOAD8_NOABORT,
|
|
BUILT_IN_ASAN_LOAD16_NOABORT,
|
|
BUILT_IN_ASAN_LOADN_NOABORT },
|
|
{ BUILT_IN_ASAN_STORE1_NOABORT,
|
|
BUILT_IN_ASAN_STORE2_NOABORT,
|
|
BUILT_IN_ASAN_STORE4_NOABORT,
|
|
BUILT_IN_ASAN_STORE8_NOABORT,
|
|
BUILT_IN_ASAN_STORE16_NOABORT,
|
|
BUILT_IN_ASAN_STOREN_NOABORT } } };
|
|
if (size_in_bytes == -1)
|
|
{
|
|
*nargs = 2;
|
|
return builtin_decl_implicit (check[recover_p][is_store][5]);
|
|
}
|
|
*nargs = 1;
|
|
int size_log2 = exact_log2 (size_in_bytes);
|
|
return builtin_decl_implicit (check[recover_p][is_store][size_log2]);
|
|
}
|
|
|
|
/* Split the current basic block and create a condition statement
|
|
insertion point right before or after the statement pointed to by
|
|
ITER. Return an iterator to the point at which the caller might
|
|
safely insert the condition statement.
|
|
|
|
THEN_BLOCK must be set to the address of an uninitialized instance
|
|
of basic_block. The function will then set *THEN_BLOCK to the
|
|
'then block' of the condition statement to be inserted by the
|
|
caller.
|
|
|
|
If CREATE_THEN_FALLTHRU_EDGE is false, no edge will be created from
|
|
*THEN_BLOCK to *FALLTHROUGH_BLOCK.
|
|
|
|
Similarly, the function will set *FALLTRHOUGH_BLOCK to the 'else
|
|
block' of the condition statement to be inserted by the caller.
|
|
|
|
Note that *FALLTHROUGH_BLOCK is a new block that contains the
|
|
statements starting from *ITER, and *THEN_BLOCK is a new empty
|
|
block.
|
|
|
|
*ITER is adjusted to point to always point to the first statement
|
|
of the basic block * FALLTHROUGH_BLOCK. That statement is the
|
|
same as what ITER was pointing to prior to calling this function,
|
|
if BEFORE_P is true; otherwise, it is its following statement. */
|
|
|
|
gimple_stmt_iterator
|
|
create_cond_insert_point (gimple_stmt_iterator *iter,
|
|
bool before_p,
|
|
bool then_more_likely_p,
|
|
bool create_then_fallthru_edge,
|
|
basic_block *then_block,
|
|
basic_block *fallthrough_block)
|
|
{
|
|
gimple_stmt_iterator gsi = *iter;
|
|
|
|
if (!gsi_end_p (gsi) && before_p)
|
|
gsi_prev (&gsi);
|
|
|
|
basic_block cur_bb = gsi_bb (*iter);
|
|
|
|
edge e = split_block (cur_bb, gsi_stmt (gsi));
|
|
|
|
/* Get a hold on the 'condition block', the 'then block' and the
|
|
'else block'. */
|
|
basic_block cond_bb = e->src;
|
|
basic_block fallthru_bb = e->dest;
|
|
basic_block then_bb = create_empty_bb (cond_bb);
|
|
if (current_loops)
|
|
{
|
|
add_bb_to_loop (then_bb, cond_bb->loop_father);
|
|
loops_state_set (LOOPS_NEED_FIXUP);
|
|
}
|
|
|
|
/* Set up the newly created 'then block'. */
|
|
e = make_edge (cond_bb, then_bb, EDGE_TRUE_VALUE);
|
|
int fallthrough_probability
|
|
= then_more_likely_p
|
|
? PROB_VERY_UNLIKELY
|
|
: PROB_ALWAYS - PROB_VERY_UNLIKELY;
|
|
e->probability = PROB_ALWAYS - fallthrough_probability;
|
|
if (create_then_fallthru_edge)
|
|
make_single_succ_edge (then_bb, fallthru_bb, EDGE_FALLTHRU);
|
|
|
|
/* Set up the fallthrough basic block. */
|
|
e = find_edge (cond_bb, fallthru_bb);
|
|
e->flags = EDGE_FALSE_VALUE;
|
|
e->count = cond_bb->count;
|
|
e->probability = fallthrough_probability;
|
|
|
|
/* Update dominance info for the newly created then_bb; note that
|
|
fallthru_bb's dominance info has already been updated by
|
|
split_bock. */
|
|
if (dom_info_available_p (CDI_DOMINATORS))
|
|
set_immediate_dominator (CDI_DOMINATORS, then_bb, cond_bb);
|
|
|
|
*then_block = then_bb;
|
|
*fallthrough_block = fallthru_bb;
|
|
*iter = gsi_start_bb (fallthru_bb);
|
|
|
|
return gsi_last_bb (cond_bb);
|
|
}
|
|
|
|
/* Insert an if condition followed by a 'then block' right before the
|
|
statement pointed to by ITER. The fallthrough block -- which is the
|
|
else block of the condition as well as the destination of the
|
|
outcoming edge of the 'then block' -- starts with the statement
|
|
pointed to by ITER.
|
|
|
|
COND is the condition of the if.
|
|
|
|
If THEN_MORE_LIKELY_P is true, the probability of the edge to the
|
|
'then block' is higher than the probability of the edge to the
|
|
fallthrough block.
|
|
|
|
Upon completion of the function, *THEN_BB is set to the newly
|
|
inserted 'then block' and similarly, *FALLTHROUGH_BB is set to the
|
|
fallthrough block.
|
|
|
|
*ITER is adjusted to still point to the same statement it was
|
|
pointing to initially. */
|
|
|
|
static void
|
|
insert_if_then_before_iter (gcond *cond,
|
|
gimple_stmt_iterator *iter,
|
|
bool then_more_likely_p,
|
|
basic_block *then_bb,
|
|
basic_block *fallthrough_bb)
|
|
{
|
|
gimple_stmt_iterator cond_insert_point =
|
|
create_cond_insert_point (iter,
|
|
/*before_p=*/true,
|
|
then_more_likely_p,
|
|
/*create_then_fallthru_edge=*/true,
|
|
then_bb,
|
|
fallthrough_bb);
|
|
gsi_insert_after (&cond_insert_point, cond, GSI_NEW_STMT);
|
|
}
|
|
|
|
/* Build (base_addr >> ASAN_SHADOW_SHIFT) + asan_shadow_offset ().
|
|
If RETURN_ADDRESS is set to true, return memory location instread
|
|
of a value in the shadow memory. */
|
|
|
|
static tree
|
|
build_shadow_mem_access (gimple_stmt_iterator *gsi, location_t location,
|
|
tree base_addr, tree shadow_ptr_type,
|
|
bool return_address = false)
|
|
{
|
|
tree t, uintptr_type = TREE_TYPE (base_addr);
|
|
tree shadow_type = TREE_TYPE (shadow_ptr_type);
|
|
gimple *g;
|
|
|
|
t = build_int_cst (uintptr_type, ASAN_SHADOW_SHIFT);
|
|
g = gimple_build_assign (make_ssa_name (uintptr_type), RSHIFT_EXPR,
|
|
base_addr, t);
|
|
gimple_set_location (g, location);
|
|
gsi_insert_after (gsi, g, GSI_NEW_STMT);
|
|
|
|
t = build_int_cst (uintptr_type, asan_shadow_offset ());
|
|
g = gimple_build_assign (make_ssa_name (uintptr_type), PLUS_EXPR,
|
|
gimple_assign_lhs (g), t);
|
|
gimple_set_location (g, location);
|
|
gsi_insert_after (gsi, g, GSI_NEW_STMT);
|
|
|
|
g = gimple_build_assign (make_ssa_name (shadow_ptr_type), NOP_EXPR,
|
|
gimple_assign_lhs (g));
|
|
gimple_set_location (g, location);
|
|
gsi_insert_after (gsi, g, GSI_NEW_STMT);
|
|
|
|
if (!return_address)
|
|
{
|
|
t = build2 (MEM_REF, shadow_type, gimple_assign_lhs (g),
|
|
build_int_cst (shadow_ptr_type, 0));
|
|
g = gimple_build_assign (make_ssa_name (shadow_type), MEM_REF, t);
|
|
gimple_set_location (g, location);
|
|
gsi_insert_after (gsi, g, GSI_NEW_STMT);
|
|
}
|
|
|
|
return gimple_assign_lhs (g);
|
|
}
|
|
|
|
/* BASE can already be an SSA_NAME; in that case, do not create a
|
|
new SSA_NAME for it. */
|
|
|
|
static tree
|
|
maybe_create_ssa_name (location_t loc, tree base, gimple_stmt_iterator *iter,
|
|
bool before_p)
|
|
{
|
|
if (TREE_CODE (base) == SSA_NAME)
|
|
return base;
|
|
gimple *g = gimple_build_assign (make_ssa_name (TREE_TYPE (base)),
|
|
TREE_CODE (base), base);
|
|
gimple_set_location (g, loc);
|
|
if (before_p)
|
|
gsi_insert_before (iter, g, GSI_SAME_STMT);
|
|
else
|
|
gsi_insert_after (iter, g, GSI_NEW_STMT);
|
|
return gimple_assign_lhs (g);
|
|
}
|
|
|
|
/* LEN can already have necessary size and precision;
|
|
in that case, do not create a new variable. */
|
|
|
|
tree
|
|
maybe_cast_to_ptrmode (location_t loc, tree len, gimple_stmt_iterator *iter,
|
|
bool before_p)
|
|
{
|
|
if (ptrofftype_p (len))
|
|
return len;
|
|
gimple *g = gimple_build_assign (make_ssa_name (pointer_sized_int_node),
|
|
NOP_EXPR, len);
|
|
gimple_set_location (g, loc);
|
|
if (before_p)
|
|
gsi_insert_before (iter, g, GSI_SAME_STMT);
|
|
else
|
|
gsi_insert_after (iter, g, GSI_NEW_STMT);
|
|
return gimple_assign_lhs (g);
|
|
}
|
|
|
|
/* Instrument the memory access instruction BASE. Insert new
|
|
statements before or after ITER.
|
|
|
|
Note that the memory access represented by BASE can be either an
|
|
SSA_NAME, or a non-SSA expression. LOCATION is the source code
|
|
location. IS_STORE is TRUE for a store, FALSE for a load.
|
|
BEFORE_P is TRUE for inserting the instrumentation code before
|
|
ITER, FALSE for inserting it after ITER. IS_SCALAR_ACCESS is TRUE
|
|
for a scalar memory access and FALSE for memory region access.
|
|
NON_ZERO_P is TRUE if memory region is guaranteed to have non-zero
|
|
length. ALIGN tells alignment of accessed memory object.
|
|
|
|
START_INSTRUMENTED and END_INSTRUMENTED are TRUE if start/end of
|
|
memory region have already been instrumented.
|
|
|
|
If BEFORE_P is TRUE, *ITER is arranged to still point to the
|
|
statement it was pointing to prior to calling this function,
|
|
otherwise, it points to the statement logically following it. */
|
|
|
|
static void
|
|
build_check_stmt (location_t loc, tree base, tree len,
|
|
HOST_WIDE_INT size_in_bytes, gimple_stmt_iterator *iter,
|
|
bool is_non_zero_len, bool before_p, bool is_store,
|
|
bool is_scalar_access, unsigned int align = 0)
|
|
{
|
|
gimple_stmt_iterator gsi = *iter;
|
|
gimple *g;
|
|
|
|
gcc_assert (!(size_in_bytes > 0 && !is_non_zero_len));
|
|
|
|
gsi = *iter;
|
|
|
|
base = unshare_expr (base);
|
|
base = maybe_create_ssa_name (loc, base, &gsi, before_p);
|
|
|
|
if (len)
|
|
{
|
|
len = unshare_expr (len);
|
|
len = maybe_cast_to_ptrmode (loc, len, iter, before_p);
|
|
}
|
|
else
|
|
{
|
|
gcc_assert (size_in_bytes != -1);
|
|
len = build_int_cst (pointer_sized_int_node, size_in_bytes);
|
|
}
|
|
|
|
if (size_in_bytes > 1)
|
|
{
|
|
if ((size_in_bytes & (size_in_bytes - 1)) != 0
|
|
|| size_in_bytes > 16)
|
|
is_scalar_access = false;
|
|
else if (align && align < size_in_bytes * BITS_PER_UNIT)
|
|
{
|
|
/* On non-strict alignment targets, if
|
|
16-byte access is just 8-byte aligned,
|
|
this will result in misaligned shadow
|
|
memory 2 byte load, but otherwise can
|
|
be handled using one read. */
|
|
if (size_in_bytes != 16
|
|
|| STRICT_ALIGNMENT
|
|
|| align < 8 * BITS_PER_UNIT)
|
|
is_scalar_access = false;
|
|
}
|
|
}
|
|
|
|
HOST_WIDE_INT flags = 0;
|
|
if (is_store)
|
|
flags |= ASAN_CHECK_STORE;
|
|
if (is_non_zero_len)
|
|
flags |= ASAN_CHECK_NON_ZERO_LEN;
|
|
if (is_scalar_access)
|
|
flags |= ASAN_CHECK_SCALAR_ACCESS;
|
|
|
|
g = gimple_build_call_internal (IFN_ASAN_CHECK, 4,
|
|
build_int_cst (integer_type_node, flags),
|
|
base, len,
|
|
build_int_cst (integer_type_node,
|
|
align / BITS_PER_UNIT));
|
|
gimple_set_location (g, loc);
|
|
if (before_p)
|
|
gsi_insert_before (&gsi, g, GSI_SAME_STMT);
|
|
else
|
|
{
|
|
gsi_insert_after (&gsi, g, GSI_NEW_STMT);
|
|
gsi_next (&gsi);
|
|
*iter = gsi;
|
|
}
|
|
}
|
|
|
|
/* If T represents a memory access, add instrumentation code before ITER.
|
|
LOCATION is source code location.
|
|
IS_STORE is either TRUE (for a store) or FALSE (for a load). */
|
|
|
|
static void
|
|
instrument_derefs (gimple_stmt_iterator *iter, tree t,
|
|
location_t location, bool is_store)
|
|
{
|
|
if (is_store && !ASAN_INSTRUMENT_WRITES)
|
|
return;
|
|
if (!is_store && !ASAN_INSTRUMENT_READS)
|
|
return;
|
|
|
|
tree type, base;
|
|
HOST_WIDE_INT size_in_bytes;
|
|
if (location == UNKNOWN_LOCATION)
|
|
location = EXPR_LOCATION (t);
|
|
|
|
type = TREE_TYPE (t);
|
|
switch (TREE_CODE (t))
|
|
{
|
|
case ARRAY_REF:
|
|
case COMPONENT_REF:
|
|
case INDIRECT_REF:
|
|
case MEM_REF:
|
|
case VAR_DECL:
|
|
case BIT_FIELD_REF:
|
|
break;
|
|
/* FALLTHRU */
|
|
default:
|
|
return;
|
|
}
|
|
|
|
size_in_bytes = int_size_in_bytes (type);
|
|
if (size_in_bytes <= 0)
|
|
return;
|
|
|
|
HOST_WIDE_INT bitsize, bitpos;
|
|
tree offset;
|
|
machine_mode mode;
|
|
int unsignedp, reversep, volatilep = 0;
|
|
tree inner = get_inner_reference (t, &bitsize, &bitpos, &offset, &mode,
|
|
&unsignedp, &reversep, &volatilep);
|
|
|
|
if (TREE_CODE (t) == COMPONENT_REF
|
|
&& DECL_BIT_FIELD_REPRESENTATIVE (TREE_OPERAND (t, 1)) != NULL_TREE)
|
|
{
|
|
tree repr = DECL_BIT_FIELD_REPRESENTATIVE (TREE_OPERAND (t, 1));
|
|
instrument_derefs (iter, build3 (COMPONENT_REF, TREE_TYPE (repr),
|
|
TREE_OPERAND (t, 0), repr,
|
|
TREE_OPERAND (t, 2)),
|
|
location, is_store);
|
|
return;
|
|
}
|
|
|
|
if (bitpos % BITS_PER_UNIT
|
|
|| bitsize != size_in_bytes * BITS_PER_UNIT)
|
|
return;
|
|
|
|
if (VAR_P (inner)
|
|
&& offset == NULL_TREE
|
|
&& bitpos >= 0
|
|
&& DECL_SIZE (inner)
|
|
&& tree_fits_shwi_p (DECL_SIZE (inner))
|
|
&& bitpos + bitsize <= tree_to_shwi (DECL_SIZE (inner)))
|
|
{
|
|
if (DECL_THREAD_LOCAL_P (inner))
|
|
return;
|
|
if (!ASAN_GLOBALS && is_global_var (inner))
|
|
return;
|
|
if (!TREE_STATIC (inner))
|
|
{
|
|
/* Automatic vars in the current function will be always
|
|
accessible. */
|
|
if (decl_function_context (inner) == current_function_decl
|
|
&& (!asan_sanitize_use_after_scope ()
|
|
|| !TREE_ADDRESSABLE (inner)))
|
|
return;
|
|
}
|
|
/* Always instrument external vars, they might be dynamically
|
|
initialized. */
|
|
else if (!DECL_EXTERNAL (inner))
|
|
{
|
|
/* For static vars if they are known not to be dynamically
|
|
initialized, they will be always accessible. */
|
|
varpool_node *vnode = varpool_node::get (inner);
|
|
if (vnode && !vnode->dynamically_initialized)
|
|
return;
|
|
}
|
|
}
|
|
|
|
base = build_fold_addr_expr (t);
|
|
if (!has_mem_ref_been_instrumented (base, size_in_bytes))
|
|
{
|
|
unsigned int align = get_object_alignment (t);
|
|
build_check_stmt (location, base, NULL_TREE, size_in_bytes, iter,
|
|
/*is_non_zero_len*/size_in_bytes > 0, /*before_p=*/true,
|
|
is_store, /*is_scalar_access*/true, align);
|
|
update_mem_ref_hash_table (base, size_in_bytes);
|
|
update_mem_ref_hash_table (t, size_in_bytes);
|
|
}
|
|
|
|
}
|
|
|
|
/* Insert a memory reference into the hash table if access length
|
|
can be determined in compile time. */
|
|
|
|
static void
|
|
maybe_update_mem_ref_hash_table (tree base, tree len)
|
|
{
|
|
if (!POINTER_TYPE_P (TREE_TYPE (base))
|
|
|| !INTEGRAL_TYPE_P (TREE_TYPE (len)))
|
|
return;
|
|
|
|
HOST_WIDE_INT size_in_bytes = tree_fits_shwi_p (len) ? tree_to_shwi (len) : -1;
|
|
|
|
if (size_in_bytes != -1)
|
|
update_mem_ref_hash_table (base, size_in_bytes);
|
|
}
|
|
|
|
/* Instrument an access to a contiguous memory region that starts at
|
|
the address pointed to by BASE, over a length of LEN (expressed in
|
|
the sizeof (*BASE) bytes). ITER points to the instruction before
|
|
which the instrumentation instructions must be inserted. LOCATION
|
|
is the source location that the instrumentation instructions must
|
|
have. If IS_STORE is true, then the memory access is a store;
|
|
otherwise, it's a load. */
|
|
|
|
static void
|
|
instrument_mem_region_access (tree base, tree len,
|
|
gimple_stmt_iterator *iter,
|
|
location_t location, bool is_store)
|
|
{
|
|
if (!POINTER_TYPE_P (TREE_TYPE (base))
|
|
|| !INTEGRAL_TYPE_P (TREE_TYPE (len))
|
|
|| integer_zerop (len))
|
|
return;
|
|
|
|
HOST_WIDE_INT size_in_bytes = tree_fits_shwi_p (len) ? tree_to_shwi (len) : -1;
|
|
|
|
if ((size_in_bytes == -1)
|
|
|| !has_mem_ref_been_instrumented (base, size_in_bytes))
|
|
{
|
|
build_check_stmt (location, base, len, size_in_bytes, iter,
|
|
/*is_non_zero_len*/size_in_bytes > 0, /*before_p*/true,
|
|
is_store, /*is_scalar_access*/false, /*align*/0);
|
|
}
|
|
|
|
maybe_update_mem_ref_hash_table (base, len);
|
|
*iter = gsi_for_stmt (gsi_stmt (*iter));
|
|
}
|
|
|
|
/* Instrument the call to a built-in memory access function that is
|
|
pointed to by the iterator ITER.
|
|
|
|
Upon completion, return TRUE iff *ITER has been advanced to the
|
|
statement following the one it was originally pointing to. */
|
|
|
|
static bool
|
|
instrument_builtin_call (gimple_stmt_iterator *iter)
|
|
{
|
|
if (!ASAN_MEMINTRIN)
|
|
return false;
|
|
|
|
bool iter_advanced_p = false;
|
|
gcall *call = as_a <gcall *> (gsi_stmt (*iter));
|
|
|
|
gcc_checking_assert (gimple_call_builtin_p (call, BUILT_IN_NORMAL));
|
|
|
|
location_t loc = gimple_location (call);
|
|
|
|
asan_mem_ref src0, src1, dest;
|
|
asan_mem_ref_init (&src0, NULL, 1);
|
|
asan_mem_ref_init (&src1, NULL, 1);
|
|
asan_mem_ref_init (&dest, NULL, 1);
|
|
|
|
tree src0_len = NULL_TREE, src1_len = NULL_TREE, dest_len = NULL_TREE;
|
|
bool src0_is_store = false, src1_is_store = false, dest_is_store = false,
|
|
dest_is_deref = false, intercepted_p = true;
|
|
|
|
if (get_mem_refs_of_builtin_call (call,
|
|
&src0, &src0_len, &src0_is_store,
|
|
&src1, &src1_len, &src1_is_store,
|
|
&dest, &dest_len, &dest_is_store,
|
|
&dest_is_deref, &intercepted_p))
|
|
{
|
|
if (dest_is_deref)
|
|
{
|
|
instrument_derefs (iter, dest.start, loc, dest_is_store);
|
|
gsi_next (iter);
|
|
iter_advanced_p = true;
|
|
}
|
|
else if (!intercepted_p
|
|
&& (src0_len || src1_len || dest_len))
|
|
{
|
|
if (src0.start != NULL_TREE)
|
|
instrument_mem_region_access (src0.start, src0_len,
|
|
iter, loc, /*is_store=*/false);
|
|
if (src1.start != NULL_TREE)
|
|
instrument_mem_region_access (src1.start, src1_len,
|
|
iter, loc, /*is_store=*/false);
|
|
if (dest.start != NULL_TREE)
|
|
instrument_mem_region_access (dest.start, dest_len,
|
|
iter, loc, /*is_store=*/true);
|
|
|
|
*iter = gsi_for_stmt (call);
|
|
gsi_next (iter);
|
|
iter_advanced_p = true;
|
|
}
|
|
else
|
|
{
|
|
if (src0.start != NULL_TREE)
|
|
maybe_update_mem_ref_hash_table (src0.start, src0_len);
|
|
if (src1.start != NULL_TREE)
|
|
maybe_update_mem_ref_hash_table (src1.start, src1_len);
|
|
if (dest.start != NULL_TREE)
|
|
maybe_update_mem_ref_hash_table (dest.start, dest_len);
|
|
}
|
|
}
|
|
return iter_advanced_p;
|
|
}
|
|
|
|
/* Instrument the assignment statement ITER if it is subject to
|
|
instrumentation. Return TRUE iff instrumentation actually
|
|
happened. In that case, the iterator ITER is advanced to the next
|
|
logical expression following the one initially pointed to by ITER,
|
|
and the relevant memory reference that which access has been
|
|
instrumented is added to the memory references hash table. */
|
|
|
|
static bool
|
|
maybe_instrument_assignment (gimple_stmt_iterator *iter)
|
|
{
|
|
gimple *s = gsi_stmt (*iter);
|
|
|
|
gcc_assert (gimple_assign_single_p (s));
|
|
|
|
tree ref_expr = NULL_TREE;
|
|
bool is_store, is_instrumented = false;
|
|
|
|
if (gimple_store_p (s))
|
|
{
|
|
ref_expr = gimple_assign_lhs (s);
|
|
is_store = true;
|
|
instrument_derefs (iter, ref_expr,
|
|
gimple_location (s),
|
|
is_store);
|
|
is_instrumented = true;
|
|
}
|
|
|
|
if (gimple_assign_load_p (s))
|
|
{
|
|
ref_expr = gimple_assign_rhs1 (s);
|
|
is_store = false;
|
|
instrument_derefs (iter, ref_expr,
|
|
gimple_location (s),
|
|
is_store);
|
|
is_instrumented = true;
|
|
}
|
|
|
|
if (is_instrumented)
|
|
gsi_next (iter);
|
|
|
|
return is_instrumented;
|
|
}
|
|
|
|
/* Instrument the function call pointed to by the iterator ITER, if it
|
|
is subject to instrumentation. At the moment, the only function
|
|
calls that are instrumented are some built-in functions that access
|
|
memory. Look at instrument_builtin_call to learn more.
|
|
|
|
Upon completion return TRUE iff *ITER was advanced to the statement
|
|
following the one it was originally pointing to. */
|
|
|
|
static bool
|
|
maybe_instrument_call (gimple_stmt_iterator *iter)
|
|
{
|
|
gimple *stmt = gsi_stmt (*iter);
|
|
bool is_builtin = gimple_call_builtin_p (stmt, BUILT_IN_NORMAL);
|
|
|
|
if (is_builtin && instrument_builtin_call (iter))
|
|
return true;
|
|
|
|
if (gimple_call_noreturn_p (stmt))
|
|
{
|
|
if (is_builtin)
|
|
{
|
|
tree callee = gimple_call_fndecl (stmt);
|
|
switch (DECL_FUNCTION_CODE (callee))
|
|
{
|
|
case BUILT_IN_UNREACHABLE:
|
|
case BUILT_IN_TRAP:
|
|
/* Don't instrument these. */
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
tree decl = builtin_decl_implicit (BUILT_IN_ASAN_HANDLE_NO_RETURN);
|
|
gimple *g = gimple_build_call (decl, 0);
|
|
gimple_set_location (g, gimple_location (stmt));
|
|
gsi_insert_before (iter, g, GSI_SAME_STMT);
|
|
}
|
|
|
|
bool instrumented = false;
|
|
if (gimple_store_p (stmt))
|
|
{
|
|
tree ref_expr = gimple_call_lhs (stmt);
|
|
instrument_derefs (iter, ref_expr,
|
|
gimple_location (stmt),
|
|
/*is_store=*/true);
|
|
|
|
instrumented = true;
|
|
}
|
|
|
|
/* Walk through gimple_call arguments and check them id needed. */
|
|
unsigned args_num = gimple_call_num_args (stmt);
|
|
for (unsigned i = 0; i < args_num; ++i)
|
|
{
|
|
tree arg = gimple_call_arg (stmt, i);
|
|
/* If ARG is not a non-aggregate register variable, compiler in general
|
|
creates temporary for it and pass it as argument to gimple call.
|
|
But in some cases, e.g. when we pass by value a small structure that
|
|
fits to register, compiler can avoid extra overhead by pulling out
|
|
these temporaries. In this case, we should check the argument. */
|
|
if (!is_gimple_reg (arg) && !is_gimple_min_invariant (arg))
|
|
{
|
|
instrument_derefs (iter, arg,
|
|
gimple_location (stmt),
|
|
/*is_store=*/false);
|
|
instrumented = true;
|
|
}
|
|
}
|
|
if (instrumented)
|
|
gsi_next (iter);
|
|
return instrumented;
|
|
}
|
|
|
|
/* Walk each instruction of all basic block and instrument those that
|
|
represent memory references: loads, stores, or function calls.
|
|
In a given basic block, this function avoids instrumenting memory
|
|
references that have already been instrumented. */
|
|
|
|
static void
|
|
transform_statements (void)
|
|
{
|
|
basic_block bb, last_bb = NULL;
|
|
gimple_stmt_iterator i;
|
|
int saved_last_basic_block = last_basic_block_for_fn (cfun);
|
|
|
|
FOR_EACH_BB_FN (bb, cfun)
|
|
{
|
|
basic_block prev_bb = bb;
|
|
|
|
if (bb->index >= saved_last_basic_block) continue;
|
|
|
|
/* Flush the mem ref hash table, if current bb doesn't have
|
|
exactly one predecessor, or if that predecessor (skipping
|
|
over asan created basic blocks) isn't the last processed
|
|
basic block. Thus we effectively flush on extended basic
|
|
block boundaries. */
|
|
while (single_pred_p (prev_bb))
|
|
{
|
|
prev_bb = single_pred (prev_bb);
|
|
if (prev_bb->index < saved_last_basic_block)
|
|
break;
|
|
}
|
|
if (prev_bb != last_bb)
|
|
empty_mem_ref_hash_table ();
|
|
last_bb = bb;
|
|
|
|
for (i = gsi_start_bb (bb); !gsi_end_p (i);)
|
|
{
|
|
gimple *s = gsi_stmt (i);
|
|
|
|
if (has_stmt_been_instrumented_p (s))
|
|
gsi_next (&i);
|
|
else if (gimple_assign_single_p (s)
|
|
&& !gimple_clobber_p (s)
|
|
&& maybe_instrument_assignment (&i))
|
|
/* Nothing to do as maybe_instrument_assignment advanced
|
|
the iterator I. */;
|
|
else if (is_gimple_call (s) && maybe_instrument_call (&i))
|
|
/* Nothing to do as maybe_instrument_call
|
|
advanced the iterator I. */;
|
|
else
|
|
{
|
|
/* No instrumentation happened.
|
|
|
|
If the current instruction is a function call that
|
|
might free something, let's forget about the memory
|
|
references that got instrumented. Otherwise we might
|
|
miss some instrumentation opportunities. Do the same
|
|
for a ASAN_MARK poisoning internal function. */
|
|
if (is_gimple_call (s)
|
|
&& (!nonfreeing_call_p (s)
|
|
|| asan_mark_p (s, ASAN_MARK_POISON)))
|
|
empty_mem_ref_hash_table ();
|
|
|
|
gsi_next (&i);
|
|
}
|
|
}
|
|
}
|
|
free_mem_ref_resources ();
|
|
}
|
|
|
|
/* Build
|
|
__asan_before_dynamic_init (module_name)
|
|
or
|
|
__asan_after_dynamic_init ()
|
|
call. */
|
|
|
|
tree
|
|
asan_dynamic_init_call (bool after_p)
|
|
{
|
|
if (shadow_ptr_types[0] == NULL_TREE)
|
|
asan_init_shadow_ptr_types ();
|
|
|
|
tree fn = builtin_decl_implicit (after_p
|
|
? BUILT_IN_ASAN_AFTER_DYNAMIC_INIT
|
|
: BUILT_IN_ASAN_BEFORE_DYNAMIC_INIT);
|
|
tree module_name_cst = NULL_TREE;
|
|
if (!after_p)
|
|
{
|
|
pretty_printer module_name_pp;
|
|
pp_string (&module_name_pp, main_input_filename);
|
|
|
|
module_name_cst = asan_pp_string (&module_name_pp);
|
|
module_name_cst = fold_convert (const_ptr_type_node,
|
|
module_name_cst);
|
|
}
|
|
|
|
return build_call_expr (fn, after_p ? 0 : 1, module_name_cst);
|
|
}
|
|
|
|
/* Build
|
|
struct __asan_global
|
|
{
|
|
const void *__beg;
|
|
uptr __size;
|
|
uptr __size_with_redzone;
|
|
const void *__name;
|
|
const void *__module_name;
|
|
uptr __has_dynamic_init;
|
|
__asan_global_source_location *__location;
|
|
char *__odr_indicator;
|
|
} type. */
|
|
|
|
static tree
|
|
asan_global_struct (void)
|
|
{
|
|
static const char *field_names[]
|
|
= { "__beg", "__size", "__size_with_redzone",
|
|
"__name", "__module_name", "__has_dynamic_init", "__location",
|
|
"__odr_indicator" };
|
|
tree fields[ARRAY_SIZE (field_names)], ret;
|
|
unsigned i;
|
|
|
|
ret = make_node (RECORD_TYPE);
|
|
for (i = 0; i < ARRAY_SIZE (field_names); i++)
|
|
{
|
|
fields[i]
|
|
= build_decl (UNKNOWN_LOCATION, FIELD_DECL,
|
|
get_identifier (field_names[i]),
|
|
(i == 0 || i == 3) ? const_ptr_type_node
|
|
: pointer_sized_int_node);
|
|
DECL_CONTEXT (fields[i]) = ret;
|
|
if (i)
|
|
DECL_CHAIN (fields[i - 1]) = fields[i];
|
|
}
|
|
tree type_decl = build_decl (input_location, TYPE_DECL,
|
|
get_identifier ("__asan_global"), ret);
|
|
DECL_IGNORED_P (type_decl) = 1;
|
|
DECL_ARTIFICIAL (type_decl) = 1;
|
|
TYPE_FIELDS (ret) = fields[0];
|
|
TYPE_NAME (ret) = type_decl;
|
|
TYPE_STUB_DECL (ret) = type_decl;
|
|
layout_type (ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Create and return odr indicator symbol for DECL.
|
|
TYPE is __asan_global struct type as returned by asan_global_struct. */
|
|
|
|
static tree
|
|
create_odr_indicator (tree decl, tree type)
|
|
{
|
|
char *name;
|
|
tree uptr = TREE_TYPE (DECL_CHAIN (TYPE_FIELDS (type)));
|
|
tree decl_name
|
|
= (HAS_DECL_ASSEMBLER_NAME_P (decl) ? DECL_ASSEMBLER_NAME (decl)
|
|
: DECL_NAME (decl));
|
|
/* DECL_NAME theoretically might be NULL. Bail out with 0 in this case. */
|
|
if (decl_name == NULL_TREE)
|
|
return build_int_cst (uptr, 0);
|
|
size_t len = strlen (IDENTIFIER_POINTER (decl_name)) + sizeof ("__odr_asan_");
|
|
name = XALLOCAVEC (char, len);
|
|
snprintf (name, len, "__odr_asan_%s", IDENTIFIER_POINTER (decl_name));
|
|
#ifndef NO_DOT_IN_LABEL
|
|
name[sizeof ("__odr_asan") - 1] = '.';
|
|
#elif !defined(NO_DOLLAR_IN_LABEL)
|
|
name[sizeof ("__odr_asan") - 1] = '$';
|
|
#endif
|
|
tree var = build_decl (UNKNOWN_LOCATION, VAR_DECL, get_identifier (name),
|
|
char_type_node);
|
|
TREE_ADDRESSABLE (var) = 1;
|
|
TREE_READONLY (var) = 0;
|
|
TREE_THIS_VOLATILE (var) = 1;
|
|
DECL_GIMPLE_REG_P (var) = 0;
|
|
DECL_ARTIFICIAL (var) = 1;
|
|
DECL_IGNORED_P (var) = 1;
|
|
TREE_STATIC (var) = 1;
|
|
TREE_PUBLIC (var) = 1;
|
|
DECL_VISIBILITY (var) = DECL_VISIBILITY (decl);
|
|
DECL_VISIBILITY_SPECIFIED (var) = DECL_VISIBILITY_SPECIFIED (decl);
|
|
|
|
TREE_USED (var) = 1;
|
|
tree ctor = build_constructor_va (TREE_TYPE (var), 1, NULL_TREE,
|
|
build_int_cst (unsigned_type_node, 0));
|
|
TREE_CONSTANT (ctor) = 1;
|
|
TREE_STATIC (ctor) = 1;
|
|
DECL_INITIAL (var) = ctor;
|
|
DECL_ATTRIBUTES (var) = tree_cons (get_identifier ("asan odr indicator"),
|
|
NULL, DECL_ATTRIBUTES (var));
|
|
make_decl_rtl (var);
|
|
varpool_node::finalize_decl (var);
|
|
return fold_convert (uptr, build_fold_addr_expr (var));
|
|
}
|
|
|
|
/* Return true if DECL, a global var, might be overridden and needs
|
|
an additional odr indicator symbol. */
|
|
|
|
static bool
|
|
asan_needs_odr_indicator_p (tree decl)
|
|
{
|
|
/* Don't emit ODR indicators for kernel because:
|
|
a) Kernel is written in C thus doesn't need ODR indicators.
|
|
b) Some kernel code may have assumptions about symbols containing specific
|
|
patterns in their names. Since ODR indicators contain original names
|
|
of symbols they are emitted for, these assumptions would be broken for
|
|
ODR indicator symbols. */
|
|
return (!(flag_sanitize & SANITIZE_KERNEL_ADDRESS)
|
|
&& !DECL_ARTIFICIAL (decl)
|
|
&& !DECL_WEAK (decl)
|
|
&& TREE_PUBLIC (decl));
|
|
}
|
|
|
|
/* Append description of a single global DECL into vector V.
|
|
TYPE is __asan_global struct type as returned by asan_global_struct. */
|
|
|
|
static void
|
|
asan_add_global (tree decl, tree type, vec<constructor_elt, va_gc> *v)
|
|
{
|
|
tree init, uptr = TREE_TYPE (DECL_CHAIN (TYPE_FIELDS (type)));
|
|
unsigned HOST_WIDE_INT size;
|
|
tree str_cst, module_name_cst, refdecl = decl;
|
|
vec<constructor_elt, va_gc> *vinner = NULL;
|
|
|
|
pretty_printer asan_pp, module_name_pp;
|
|
|
|
if (DECL_NAME (decl))
|
|
pp_tree_identifier (&asan_pp, DECL_NAME (decl));
|
|
else
|
|
pp_string (&asan_pp, "<unknown>");
|
|
str_cst = asan_pp_string (&asan_pp);
|
|
|
|
pp_string (&module_name_pp, main_input_filename);
|
|
module_name_cst = asan_pp_string (&module_name_pp);
|
|
|
|
if (asan_needs_local_alias (decl))
|
|
{
|
|
char buf[20];
|
|
ASM_GENERATE_INTERNAL_LABEL (buf, "LASAN", vec_safe_length (v) + 1);
|
|
refdecl = build_decl (DECL_SOURCE_LOCATION (decl),
|
|
VAR_DECL, get_identifier (buf), TREE_TYPE (decl));
|
|
TREE_ADDRESSABLE (refdecl) = TREE_ADDRESSABLE (decl);
|
|
TREE_READONLY (refdecl) = TREE_READONLY (decl);
|
|
TREE_THIS_VOLATILE (refdecl) = TREE_THIS_VOLATILE (decl);
|
|
DECL_GIMPLE_REG_P (refdecl) = DECL_GIMPLE_REG_P (decl);
|
|
DECL_ARTIFICIAL (refdecl) = DECL_ARTIFICIAL (decl);
|
|
DECL_IGNORED_P (refdecl) = DECL_IGNORED_P (decl);
|
|
TREE_STATIC (refdecl) = 1;
|
|
TREE_PUBLIC (refdecl) = 0;
|
|
TREE_USED (refdecl) = 1;
|
|
assemble_alias (refdecl, DECL_ASSEMBLER_NAME (decl));
|
|
}
|
|
|
|
tree odr_indicator_ptr
|
|
= (asan_needs_odr_indicator_p (decl) ? create_odr_indicator (decl, type)
|
|
: build_int_cst (uptr, 0));
|
|
CONSTRUCTOR_APPEND_ELT (vinner, NULL_TREE,
|
|
fold_convert (const_ptr_type_node,
|
|
build_fold_addr_expr (refdecl)));
|
|
size = tree_to_uhwi (DECL_SIZE_UNIT (decl));
|
|
CONSTRUCTOR_APPEND_ELT (vinner, NULL_TREE, build_int_cst (uptr, size));
|
|
size += asan_red_zone_size (size);
|
|
CONSTRUCTOR_APPEND_ELT (vinner, NULL_TREE, build_int_cst (uptr, size));
|
|
CONSTRUCTOR_APPEND_ELT (vinner, NULL_TREE,
|
|
fold_convert (const_ptr_type_node, str_cst));
|
|
CONSTRUCTOR_APPEND_ELT (vinner, NULL_TREE,
|
|
fold_convert (const_ptr_type_node, module_name_cst));
|
|
varpool_node *vnode = varpool_node::get (decl);
|
|
int has_dynamic_init = 0;
|
|
/* FIXME: Enable initialization order fiasco detection in LTO mode once
|
|
proper fix for PR 79061 will be applied. */
|
|
if (!in_lto_p)
|
|
has_dynamic_init = vnode ? vnode->dynamically_initialized : 0;
|
|
CONSTRUCTOR_APPEND_ELT (vinner, NULL_TREE,
|
|
build_int_cst (uptr, has_dynamic_init));
|
|
tree locptr = NULL_TREE;
|
|
location_t loc = DECL_SOURCE_LOCATION (decl);
|
|
expanded_location xloc = expand_location (loc);
|
|
if (xloc.file != NULL)
|
|
{
|
|
static int lasanloccnt = 0;
|
|
char buf[25];
|
|
ASM_GENERATE_INTERNAL_LABEL (buf, "LASANLOC", ++lasanloccnt);
|
|
tree var = build_decl (UNKNOWN_LOCATION, VAR_DECL, get_identifier (buf),
|
|
ubsan_get_source_location_type ());
|
|
TREE_STATIC (var) = 1;
|
|
TREE_PUBLIC (var) = 0;
|
|
DECL_ARTIFICIAL (var) = 1;
|
|
DECL_IGNORED_P (var) = 1;
|
|
pretty_printer filename_pp;
|
|
pp_string (&filename_pp, xloc.file);
|
|
tree str = asan_pp_string (&filename_pp);
|
|
tree ctor = build_constructor_va (TREE_TYPE (var), 3,
|
|
NULL_TREE, str, NULL_TREE,
|
|
build_int_cst (unsigned_type_node,
|
|
xloc.line), NULL_TREE,
|
|
build_int_cst (unsigned_type_node,
|
|
xloc.column));
|
|
TREE_CONSTANT (ctor) = 1;
|
|
TREE_STATIC (ctor) = 1;
|
|
DECL_INITIAL (var) = ctor;
|
|
varpool_node::finalize_decl (var);
|
|
locptr = fold_convert (uptr, build_fold_addr_expr (var));
|
|
}
|
|
else
|
|
locptr = build_int_cst (uptr, 0);
|
|
CONSTRUCTOR_APPEND_ELT (vinner, NULL_TREE, locptr);
|
|
CONSTRUCTOR_APPEND_ELT (vinner, NULL_TREE, odr_indicator_ptr);
|
|
init = build_constructor (type, vinner);
|
|
CONSTRUCTOR_APPEND_ELT (v, NULL_TREE, init);
|
|
}
|
|
|
|
/* Initialize sanitizer.def builtins if the FE hasn't initialized them. */
|
|
void
|
|
initialize_sanitizer_builtins (void)
|
|
{
|
|
tree decl;
|
|
|
|
if (builtin_decl_implicit_p (BUILT_IN_ASAN_INIT))
|
|
return;
|
|
|
|
tree BT_FN_VOID = build_function_type_list (void_type_node, NULL_TREE);
|
|
tree BT_FN_VOID_PTR
|
|
= build_function_type_list (void_type_node, ptr_type_node, NULL_TREE);
|
|
tree BT_FN_VOID_CONST_PTR
|
|
= build_function_type_list (void_type_node, const_ptr_type_node, NULL_TREE);
|
|
tree BT_FN_VOID_PTR_PTR
|
|
= build_function_type_list (void_type_node, ptr_type_node,
|
|
ptr_type_node, NULL_TREE);
|
|
tree BT_FN_VOID_PTR_PTR_PTR
|
|
= build_function_type_list (void_type_node, ptr_type_node,
|
|
ptr_type_node, ptr_type_node, NULL_TREE);
|
|
tree BT_FN_VOID_PTR_PTRMODE
|
|
= build_function_type_list (void_type_node, ptr_type_node,
|
|
pointer_sized_int_node, NULL_TREE);
|
|
tree BT_FN_VOID_INT
|
|
= build_function_type_list (void_type_node, integer_type_node, NULL_TREE);
|
|
tree BT_FN_SIZE_CONST_PTR_INT
|
|
= build_function_type_list (size_type_node, const_ptr_type_node,
|
|
integer_type_node, NULL_TREE);
|
|
tree BT_FN_BOOL_VPTR_PTR_IX_INT_INT[5];
|
|
tree BT_FN_IX_CONST_VPTR_INT[5];
|
|
tree BT_FN_IX_VPTR_IX_INT[5];
|
|
tree BT_FN_VOID_VPTR_IX_INT[5];
|
|
tree vptr
|
|
= build_pointer_type (build_qualified_type (void_type_node,
|
|
TYPE_QUAL_VOLATILE));
|
|
tree cvptr
|
|
= build_pointer_type (build_qualified_type (void_type_node,
|
|
TYPE_QUAL_VOLATILE
|
|
|TYPE_QUAL_CONST));
|
|
tree boolt
|
|
= lang_hooks.types.type_for_size (BOOL_TYPE_SIZE, 1);
|
|
int i;
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
tree ix = build_nonstandard_integer_type (BITS_PER_UNIT * (1 << i), 1);
|
|
BT_FN_BOOL_VPTR_PTR_IX_INT_INT[i]
|
|
= build_function_type_list (boolt, vptr, ptr_type_node, ix,
|
|
integer_type_node, integer_type_node,
|
|
NULL_TREE);
|
|
BT_FN_IX_CONST_VPTR_INT[i]
|
|
= build_function_type_list (ix, cvptr, integer_type_node, NULL_TREE);
|
|
BT_FN_IX_VPTR_IX_INT[i]
|
|
= build_function_type_list (ix, vptr, ix, integer_type_node,
|
|
NULL_TREE);
|
|
BT_FN_VOID_VPTR_IX_INT[i]
|
|
= build_function_type_list (void_type_node, vptr, ix,
|
|
integer_type_node, NULL_TREE);
|
|
}
|
|
#define BT_FN_BOOL_VPTR_PTR_I1_INT_INT BT_FN_BOOL_VPTR_PTR_IX_INT_INT[0]
|
|
#define BT_FN_I1_CONST_VPTR_INT BT_FN_IX_CONST_VPTR_INT[0]
|
|
#define BT_FN_I1_VPTR_I1_INT BT_FN_IX_VPTR_IX_INT[0]
|
|
#define BT_FN_VOID_VPTR_I1_INT BT_FN_VOID_VPTR_IX_INT[0]
|
|
#define BT_FN_BOOL_VPTR_PTR_I2_INT_INT BT_FN_BOOL_VPTR_PTR_IX_INT_INT[1]
|
|
#define BT_FN_I2_CONST_VPTR_INT BT_FN_IX_CONST_VPTR_INT[1]
|
|
#define BT_FN_I2_VPTR_I2_INT BT_FN_IX_VPTR_IX_INT[1]
|
|
#define BT_FN_VOID_VPTR_I2_INT BT_FN_VOID_VPTR_IX_INT[1]
|
|
#define BT_FN_BOOL_VPTR_PTR_I4_INT_INT BT_FN_BOOL_VPTR_PTR_IX_INT_INT[2]
|
|
#define BT_FN_I4_CONST_VPTR_INT BT_FN_IX_CONST_VPTR_INT[2]
|
|
#define BT_FN_I4_VPTR_I4_INT BT_FN_IX_VPTR_IX_INT[2]
|
|
#define BT_FN_VOID_VPTR_I4_INT BT_FN_VOID_VPTR_IX_INT[2]
|
|
#define BT_FN_BOOL_VPTR_PTR_I8_INT_INT BT_FN_BOOL_VPTR_PTR_IX_INT_INT[3]
|
|
#define BT_FN_I8_CONST_VPTR_INT BT_FN_IX_CONST_VPTR_INT[3]
|
|
#define BT_FN_I8_VPTR_I8_INT BT_FN_IX_VPTR_IX_INT[3]
|
|
#define BT_FN_VOID_VPTR_I8_INT BT_FN_VOID_VPTR_IX_INT[3]
|
|
#define BT_FN_BOOL_VPTR_PTR_I16_INT_INT BT_FN_BOOL_VPTR_PTR_IX_INT_INT[4]
|
|
#define BT_FN_I16_CONST_VPTR_INT BT_FN_IX_CONST_VPTR_INT[4]
|
|
#define BT_FN_I16_VPTR_I16_INT BT_FN_IX_VPTR_IX_INT[4]
|
|
#define BT_FN_VOID_VPTR_I16_INT BT_FN_VOID_VPTR_IX_INT[4]
|
|
#undef ATTR_NOTHROW_LEAF_LIST
|
|
#define ATTR_NOTHROW_LEAF_LIST ECF_NOTHROW | ECF_LEAF
|
|
#undef ATTR_TMPURE_NOTHROW_LEAF_LIST
|
|
#define ATTR_TMPURE_NOTHROW_LEAF_LIST ECF_TM_PURE | ATTR_NOTHROW_LEAF_LIST
|
|
#undef ATTR_NORETURN_NOTHROW_LEAF_LIST
|
|
#define ATTR_NORETURN_NOTHROW_LEAF_LIST ECF_NORETURN | ATTR_NOTHROW_LEAF_LIST
|
|
#undef ATTR_CONST_NORETURN_NOTHROW_LEAF_LIST
|
|
#define ATTR_CONST_NORETURN_NOTHROW_LEAF_LIST \
|
|
ECF_CONST | ATTR_NORETURN_NOTHROW_LEAF_LIST
|
|
#undef ATTR_TMPURE_NORETURN_NOTHROW_LEAF_LIST
|
|
#define ATTR_TMPURE_NORETURN_NOTHROW_LEAF_LIST \
|
|
ECF_TM_PURE | ATTR_NORETURN_NOTHROW_LEAF_LIST
|
|
#undef ATTR_COLD_NOTHROW_LEAF_LIST
|
|
#define ATTR_COLD_NOTHROW_LEAF_LIST \
|
|
/* ECF_COLD missing */ ATTR_NOTHROW_LEAF_LIST
|
|
#undef ATTR_COLD_NORETURN_NOTHROW_LEAF_LIST
|
|
#define ATTR_COLD_NORETURN_NOTHROW_LEAF_LIST \
|
|
/* ECF_COLD missing */ ATTR_NORETURN_NOTHROW_LEAF_LIST
|
|
#undef ATTR_COLD_CONST_NORETURN_NOTHROW_LEAF_LIST
|
|
#define ATTR_COLD_CONST_NORETURN_NOTHROW_LEAF_LIST \
|
|
/* ECF_COLD missing */ ATTR_CONST_NORETURN_NOTHROW_LEAF_LIST
|
|
#undef ATTR_PURE_NOTHROW_LEAF_LIST
|
|
#define ATTR_PURE_NOTHROW_LEAF_LIST ECF_PURE | ATTR_NOTHROW_LEAF_LIST
|
|
#undef DEF_BUILTIN_STUB
|
|
#define DEF_BUILTIN_STUB(ENUM, NAME)
|
|
#undef DEF_SANITIZER_BUILTIN
|
|
#define DEF_SANITIZER_BUILTIN(ENUM, NAME, TYPE, ATTRS) \
|
|
do { \
|
|
decl = add_builtin_function ("__builtin_" NAME, TYPE, ENUM, \
|
|
BUILT_IN_NORMAL, NAME, NULL_TREE); \
|
|
set_call_expr_flags (decl, ATTRS); \
|
|
set_builtin_decl (ENUM, decl, true); \
|
|
} while (0);
|
|
|
|
#include "sanitizer.def"
|
|
|
|
/* -fsanitize=object-size uses __builtin_object_size, but that might
|
|
not be available for e.g. Fortran at this point. We use
|
|
DEF_SANITIZER_BUILTIN here only as a convenience macro. */
|
|
if ((flag_sanitize & SANITIZE_OBJECT_SIZE)
|
|
&& !builtin_decl_implicit_p (BUILT_IN_OBJECT_SIZE))
|
|
DEF_SANITIZER_BUILTIN (BUILT_IN_OBJECT_SIZE, "object_size",
|
|
BT_FN_SIZE_CONST_PTR_INT,
|
|
ATTR_PURE_NOTHROW_LEAF_LIST)
|
|
|
|
#undef DEF_SANITIZER_BUILTIN
|
|
#undef DEF_BUILTIN_STUB
|
|
}
|
|
|
|
/* Called via htab_traverse. Count number of emitted
|
|
STRING_CSTs in the constant hash table. */
|
|
|
|
int
|
|
count_string_csts (constant_descriptor_tree **slot,
|
|
unsigned HOST_WIDE_INT *data)
|
|
{
|
|
struct constant_descriptor_tree *desc = *slot;
|
|
if (TREE_CODE (desc->value) == STRING_CST
|
|
&& TREE_ASM_WRITTEN (desc->value)
|
|
&& asan_protect_global (desc->value))
|
|
++*data;
|
|
return 1;
|
|
}
|
|
|
|
/* Helper structure to pass two parameters to
|
|
add_string_csts. */
|
|
|
|
struct asan_add_string_csts_data
|
|
{
|
|
tree type;
|
|
vec<constructor_elt, va_gc> *v;
|
|
};
|
|
|
|
/* Called via hash_table::traverse. Call asan_add_global
|
|
on emitted STRING_CSTs from the constant hash table. */
|
|
|
|
int
|
|
add_string_csts (constant_descriptor_tree **slot,
|
|
asan_add_string_csts_data *aascd)
|
|
{
|
|
struct constant_descriptor_tree *desc = *slot;
|
|
if (TREE_CODE (desc->value) == STRING_CST
|
|
&& TREE_ASM_WRITTEN (desc->value)
|
|
&& asan_protect_global (desc->value))
|
|
{
|
|
asan_add_global (SYMBOL_REF_DECL (XEXP (desc->rtl, 0)),
|
|
aascd->type, aascd->v);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Needs to be GTY(()), because cgraph_build_static_cdtor may
|
|
invoke ggc_collect. */
|
|
static GTY(()) tree asan_ctor_statements;
|
|
|
|
/* Module-level instrumentation.
|
|
- Insert __asan_init_vN() into the list of CTORs.
|
|
- TODO: insert redzones around globals.
|
|
*/
|
|
|
|
void
|
|
asan_finish_file (void)
|
|
{
|
|
varpool_node *vnode;
|
|
unsigned HOST_WIDE_INT gcount = 0;
|
|
|
|
if (shadow_ptr_types[0] == NULL_TREE)
|
|
asan_init_shadow_ptr_types ();
|
|
/* Avoid instrumenting code in the asan ctors/dtors.
|
|
We don't need to insert padding after the description strings,
|
|
nor after .LASAN* array. */
|
|
flag_sanitize &= ~SANITIZE_ADDRESS;
|
|
|
|
/* For user-space we want asan constructors to run first.
|
|
Linux kernel does not support priorities other than default, and the only
|
|
other user of constructors is coverage. So we run with the default
|
|
priority. */
|
|
int priority = flag_sanitize & SANITIZE_USER_ADDRESS
|
|
? MAX_RESERVED_INIT_PRIORITY - 1 : DEFAULT_INIT_PRIORITY;
|
|
|
|
if (flag_sanitize & SANITIZE_USER_ADDRESS)
|
|
{
|
|
tree fn = builtin_decl_implicit (BUILT_IN_ASAN_INIT);
|
|
append_to_statement_list (build_call_expr (fn, 0), &asan_ctor_statements);
|
|
fn = builtin_decl_implicit (BUILT_IN_ASAN_VERSION_MISMATCH_CHECK);
|
|
append_to_statement_list (build_call_expr (fn, 0), &asan_ctor_statements);
|
|
}
|
|
FOR_EACH_DEFINED_VARIABLE (vnode)
|
|
if (TREE_ASM_WRITTEN (vnode->decl)
|
|
&& asan_protect_global (vnode->decl))
|
|
++gcount;
|
|
hash_table<tree_descriptor_hasher> *const_desc_htab = constant_pool_htab ();
|
|
const_desc_htab->traverse<unsigned HOST_WIDE_INT *, count_string_csts>
|
|
(&gcount);
|
|
if (gcount)
|
|
{
|
|
tree type = asan_global_struct (), var, ctor;
|
|
tree dtor_statements = NULL_TREE;
|
|
vec<constructor_elt, va_gc> *v;
|
|
char buf[20];
|
|
|
|
type = build_array_type_nelts (type, gcount);
|
|
ASM_GENERATE_INTERNAL_LABEL (buf, "LASAN", 0);
|
|
var = build_decl (UNKNOWN_LOCATION, VAR_DECL, get_identifier (buf),
|
|
type);
|
|
TREE_STATIC (var) = 1;
|
|
TREE_PUBLIC (var) = 0;
|
|
DECL_ARTIFICIAL (var) = 1;
|
|
DECL_IGNORED_P (var) = 1;
|
|
vec_alloc (v, gcount);
|
|
FOR_EACH_DEFINED_VARIABLE (vnode)
|
|
if (TREE_ASM_WRITTEN (vnode->decl)
|
|
&& asan_protect_global (vnode->decl))
|
|
asan_add_global (vnode->decl, TREE_TYPE (type), v);
|
|
struct asan_add_string_csts_data aascd;
|
|
aascd.type = TREE_TYPE (type);
|
|
aascd.v = v;
|
|
const_desc_htab->traverse<asan_add_string_csts_data *, add_string_csts>
|
|
(&aascd);
|
|
ctor = build_constructor (type, v);
|
|
TREE_CONSTANT (ctor) = 1;
|
|
TREE_STATIC (ctor) = 1;
|
|
DECL_INITIAL (var) = ctor;
|
|
varpool_node::finalize_decl (var);
|
|
|
|
tree fn = builtin_decl_implicit (BUILT_IN_ASAN_REGISTER_GLOBALS);
|
|
tree gcount_tree = build_int_cst (pointer_sized_int_node, gcount);
|
|
append_to_statement_list (build_call_expr (fn, 2,
|
|
build_fold_addr_expr (var),
|
|
gcount_tree),
|
|
&asan_ctor_statements);
|
|
|
|
fn = builtin_decl_implicit (BUILT_IN_ASAN_UNREGISTER_GLOBALS);
|
|
append_to_statement_list (build_call_expr (fn, 2,
|
|
build_fold_addr_expr (var),
|
|
gcount_tree),
|
|
&dtor_statements);
|
|
cgraph_build_static_cdtor ('D', dtor_statements, priority);
|
|
}
|
|
if (asan_ctor_statements)
|
|
cgraph_build_static_cdtor ('I', asan_ctor_statements, priority);
|
|
flag_sanitize |= SANITIZE_ADDRESS;
|
|
}
|
|
|
|
/* Poison or unpoison (depending on IS_CLOBBER variable) shadow memory based
|
|
on SHADOW address. Newly added statements will be added to ITER with
|
|
given location LOC. We mark SIZE bytes in shadow memory, where
|
|
LAST_CHUNK_SIZE is greater than zero in situation where we are at the
|
|
end of a variable. */
|
|
|
|
static void
|
|
asan_store_shadow_bytes (gimple_stmt_iterator *iter, location_t loc,
|
|
tree shadow,
|
|
unsigned HOST_WIDE_INT base_addr_offset,
|
|
bool is_clobber, unsigned size,
|
|
unsigned last_chunk_size)
|
|
{
|
|
tree shadow_ptr_type;
|
|
|
|
switch (size)
|
|
{
|
|
case 1:
|
|
shadow_ptr_type = shadow_ptr_types[0];
|
|
break;
|
|
case 2:
|
|
shadow_ptr_type = shadow_ptr_types[1];
|
|
break;
|
|
case 4:
|
|
shadow_ptr_type = shadow_ptr_types[2];
|
|
break;
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
|
|
unsigned char c = (char) is_clobber ? ASAN_STACK_MAGIC_USE_AFTER_SCOPE : 0;
|
|
unsigned HOST_WIDE_INT val = 0;
|
|
unsigned last_pos = size;
|
|
if (last_chunk_size && !is_clobber)
|
|
last_pos = BYTES_BIG_ENDIAN ? 0 : size - 1;
|
|
for (unsigned i = 0; i < size; ++i)
|
|
{
|
|
unsigned char shadow_c = c;
|
|
if (i == last_pos)
|
|
shadow_c = last_chunk_size;
|
|
val |= (unsigned HOST_WIDE_INT) shadow_c << (BITS_PER_UNIT * i);
|
|
}
|
|
|
|
/* Handle last chunk in unpoisoning. */
|
|
tree magic = build_int_cst (TREE_TYPE (shadow_ptr_type), val);
|
|
|
|
tree dest = build2 (MEM_REF, TREE_TYPE (shadow_ptr_type), shadow,
|
|
build_int_cst (shadow_ptr_type, base_addr_offset));
|
|
|
|
gimple *g = gimple_build_assign (dest, magic);
|
|
gimple_set_location (g, loc);
|
|
gsi_insert_after (iter, g, GSI_NEW_STMT);
|
|
}
|
|
|
|
/* Expand the ASAN_MARK builtins. */
|
|
|
|
bool
|
|
asan_expand_mark_ifn (gimple_stmt_iterator *iter)
|
|
{
|
|
gimple *g = gsi_stmt (*iter);
|
|
location_t loc = gimple_location (g);
|
|
HOST_WIDE_INT flag = tree_to_shwi (gimple_call_arg (g, 0));
|
|
bool is_poison = ((asan_mark_flags)flag) == ASAN_MARK_POISON;
|
|
|
|
tree base = gimple_call_arg (g, 1);
|
|
gcc_checking_assert (TREE_CODE (base) == ADDR_EXPR);
|
|
tree decl = TREE_OPERAND (base, 0);
|
|
|
|
/* For a nested function, we can have: ASAN_MARK (2, &FRAME.2.fp_input, 4) */
|
|
if (TREE_CODE (decl) == COMPONENT_REF
|
|
&& DECL_NONLOCAL_FRAME (TREE_OPERAND (decl, 0)))
|
|
decl = TREE_OPERAND (decl, 0);
|
|
|
|
gcc_checking_assert (TREE_CODE (decl) == VAR_DECL);
|
|
if (asan_handled_variables == NULL)
|
|
asan_handled_variables = new hash_set<tree> (16);
|
|
asan_handled_variables->add (decl);
|
|
tree len = gimple_call_arg (g, 2);
|
|
|
|
gcc_assert (tree_fits_shwi_p (len));
|
|
unsigned HOST_WIDE_INT size_in_bytes = tree_to_shwi (len);
|
|
gcc_assert (size_in_bytes);
|
|
|
|
g = gimple_build_assign (make_ssa_name (pointer_sized_int_node),
|
|
NOP_EXPR, base);
|
|
gimple_set_location (g, loc);
|
|
gsi_replace (iter, g, false);
|
|
tree base_addr = gimple_assign_lhs (g);
|
|
|
|
/* Generate direct emission if size_in_bytes is small. */
|
|
if (size_in_bytes <= ASAN_PARAM_USE_AFTER_SCOPE_DIRECT_EMISSION_THRESHOLD)
|
|
{
|
|
unsigned HOST_WIDE_INT shadow_size = shadow_mem_size (size_in_bytes);
|
|
|
|
tree shadow = build_shadow_mem_access (iter, loc, base_addr,
|
|
shadow_ptr_types[0], true);
|
|
|
|
for (unsigned HOST_WIDE_INT offset = 0; offset < shadow_size;)
|
|
{
|
|
unsigned size = 1;
|
|
if (shadow_size - offset >= 4)
|
|
size = 4;
|
|
else if (shadow_size - offset >= 2)
|
|
size = 2;
|
|
|
|
unsigned HOST_WIDE_INT last_chunk_size = 0;
|
|
unsigned HOST_WIDE_INT s = (offset + size) * ASAN_SHADOW_GRANULARITY;
|
|
if (s > size_in_bytes)
|
|
last_chunk_size = ASAN_SHADOW_GRANULARITY - (s - size_in_bytes);
|
|
|
|
asan_store_shadow_bytes (iter, loc, shadow, offset, is_poison,
|
|
size, last_chunk_size);
|
|
offset += size;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g = gimple_build_assign (make_ssa_name (pointer_sized_int_node),
|
|
NOP_EXPR, len);
|
|
gimple_set_location (g, loc);
|
|
gsi_insert_before (iter, g, GSI_SAME_STMT);
|
|
tree sz_arg = gimple_assign_lhs (g);
|
|
|
|
tree fun
|
|
= builtin_decl_implicit (is_poison ? BUILT_IN_ASAN_POISON_STACK_MEMORY
|
|
: BUILT_IN_ASAN_UNPOISON_STACK_MEMORY);
|
|
g = gimple_build_call (fun, 2, base_addr, sz_arg);
|
|
gimple_set_location (g, loc);
|
|
gsi_insert_after (iter, g, GSI_NEW_STMT);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Expand the ASAN_{LOAD,STORE} builtins. */
|
|
|
|
bool
|
|
asan_expand_check_ifn (gimple_stmt_iterator *iter, bool use_calls)
|
|
{
|
|
gimple *g = gsi_stmt (*iter);
|
|
location_t loc = gimple_location (g);
|
|
bool recover_p;
|
|
if (flag_sanitize & SANITIZE_USER_ADDRESS)
|
|
recover_p = (flag_sanitize_recover & SANITIZE_USER_ADDRESS) != 0;
|
|
else
|
|
recover_p = (flag_sanitize_recover & SANITIZE_KERNEL_ADDRESS) != 0;
|
|
|
|
HOST_WIDE_INT flags = tree_to_shwi (gimple_call_arg (g, 0));
|
|
gcc_assert (flags < ASAN_CHECK_LAST);
|
|
bool is_scalar_access = (flags & ASAN_CHECK_SCALAR_ACCESS) != 0;
|
|
bool is_store = (flags & ASAN_CHECK_STORE) != 0;
|
|
bool is_non_zero_len = (flags & ASAN_CHECK_NON_ZERO_LEN) != 0;
|
|
|
|
tree base = gimple_call_arg (g, 1);
|
|
tree len = gimple_call_arg (g, 2);
|
|
HOST_WIDE_INT align = tree_to_shwi (gimple_call_arg (g, 3));
|
|
|
|
HOST_WIDE_INT size_in_bytes
|
|
= is_scalar_access && tree_fits_shwi_p (len) ? tree_to_shwi (len) : -1;
|
|
|
|
if (use_calls)
|
|
{
|
|
/* Instrument using callbacks. */
|
|
gimple *g = gimple_build_assign (make_ssa_name (pointer_sized_int_node),
|
|
NOP_EXPR, base);
|
|
gimple_set_location (g, loc);
|
|
gsi_insert_before (iter, g, GSI_SAME_STMT);
|
|
tree base_addr = gimple_assign_lhs (g);
|
|
|
|
int nargs;
|
|
tree fun = check_func (is_store, recover_p, size_in_bytes, &nargs);
|
|
if (nargs == 1)
|
|
g = gimple_build_call (fun, 1, base_addr);
|
|
else
|
|
{
|
|
gcc_assert (nargs == 2);
|
|
g = gimple_build_assign (make_ssa_name (pointer_sized_int_node),
|
|
NOP_EXPR, len);
|
|
gimple_set_location (g, loc);
|
|
gsi_insert_before (iter, g, GSI_SAME_STMT);
|
|
tree sz_arg = gimple_assign_lhs (g);
|
|
g = gimple_build_call (fun, nargs, base_addr, sz_arg);
|
|
}
|
|
gimple_set_location (g, loc);
|
|
gsi_replace (iter, g, false);
|
|
return false;
|
|
}
|
|
|
|
HOST_WIDE_INT real_size_in_bytes = size_in_bytes == -1 ? 1 : size_in_bytes;
|
|
|
|
tree shadow_ptr_type = shadow_ptr_types[real_size_in_bytes == 16 ? 1 : 0];
|
|
tree shadow_type = TREE_TYPE (shadow_ptr_type);
|
|
|
|
gimple_stmt_iterator gsi = *iter;
|
|
|
|
if (!is_non_zero_len)
|
|
{
|
|
/* So, the length of the memory area to asan-protect is
|
|
non-constant. Let's guard the generated instrumentation code
|
|
like:
|
|
|
|
if (len != 0)
|
|
{
|
|
//asan instrumentation code goes here.
|
|
}
|
|
// falltrough instructions, starting with *ITER. */
|
|
|
|
g = gimple_build_cond (NE_EXPR,
|
|
len,
|
|
build_int_cst (TREE_TYPE (len), 0),
|
|
NULL_TREE, NULL_TREE);
|
|
gimple_set_location (g, loc);
|
|
|
|
basic_block then_bb, fallthrough_bb;
|
|
insert_if_then_before_iter (as_a <gcond *> (g), iter,
|
|
/*then_more_likely_p=*/true,
|
|
&then_bb, &fallthrough_bb);
|
|
/* Note that fallthrough_bb starts with the statement that was
|
|
pointed to by ITER. */
|
|
|
|
/* The 'then block' of the 'if (len != 0) condition is where
|
|
we'll generate the asan instrumentation code now. */
|
|
gsi = gsi_last_bb (then_bb);
|
|
}
|
|
|
|
/* Get an iterator on the point where we can add the condition
|
|
statement for the instrumentation. */
|
|
basic_block then_bb, else_bb;
|
|
gsi = create_cond_insert_point (&gsi, /*before_p*/false,
|
|
/*then_more_likely_p=*/false,
|
|
/*create_then_fallthru_edge*/recover_p,
|
|
&then_bb,
|
|
&else_bb);
|
|
|
|
g = gimple_build_assign (make_ssa_name (pointer_sized_int_node),
|
|
NOP_EXPR, base);
|
|
gimple_set_location (g, loc);
|
|
gsi_insert_before (&gsi, g, GSI_NEW_STMT);
|
|
tree base_addr = gimple_assign_lhs (g);
|
|
|
|
tree t = NULL_TREE;
|
|
if (real_size_in_bytes >= 8)
|
|
{
|
|
tree shadow = build_shadow_mem_access (&gsi, loc, base_addr,
|
|
shadow_ptr_type);
|
|
t = shadow;
|
|
}
|
|
else
|
|
{
|
|
/* Slow path for 1, 2 and 4 byte accesses. */
|
|
/* Test (shadow != 0)
|
|
& ((base_addr & 7) + (real_size_in_bytes - 1)) >= shadow). */
|
|
tree shadow = build_shadow_mem_access (&gsi, loc, base_addr,
|
|
shadow_ptr_type);
|
|
gimple *shadow_test = build_assign (NE_EXPR, shadow, 0);
|
|
gimple_seq seq = NULL;
|
|
gimple_seq_add_stmt (&seq, shadow_test);
|
|
/* Aligned (>= 8 bytes) can test just
|
|
(real_size_in_bytes - 1 >= shadow), as base_addr & 7 is known
|
|
to be 0. */
|
|
if (align < 8)
|
|
{
|
|
gimple_seq_add_stmt (&seq, build_assign (BIT_AND_EXPR,
|
|
base_addr, 7));
|
|
gimple_seq_add_stmt (&seq,
|
|
build_type_cast (shadow_type,
|
|
gimple_seq_last (seq)));
|
|
if (real_size_in_bytes > 1)
|
|
gimple_seq_add_stmt (&seq,
|
|
build_assign (PLUS_EXPR,
|
|
gimple_seq_last (seq),
|
|
real_size_in_bytes - 1));
|
|
t = gimple_assign_lhs (gimple_seq_last_stmt (seq));
|
|
}
|
|
else
|
|
t = build_int_cst (shadow_type, real_size_in_bytes - 1);
|
|
gimple_seq_add_stmt (&seq, build_assign (GE_EXPR, t, shadow));
|
|
gimple_seq_add_stmt (&seq, build_assign (BIT_AND_EXPR, shadow_test,
|
|
gimple_seq_last (seq)));
|
|
t = gimple_assign_lhs (gimple_seq_last (seq));
|
|
gimple_seq_set_location (seq, loc);
|
|
gsi_insert_seq_after (&gsi, seq, GSI_CONTINUE_LINKING);
|
|
|
|
/* For non-constant, misaligned or otherwise weird access sizes,
|
|
check first and last byte. */
|
|
if (size_in_bytes == -1)
|
|
{
|
|
g = gimple_build_assign (make_ssa_name (pointer_sized_int_node),
|
|
MINUS_EXPR, len,
|
|
build_int_cst (pointer_sized_int_node, 1));
|
|
gimple_set_location (g, loc);
|
|
gsi_insert_after (&gsi, g, GSI_NEW_STMT);
|
|
tree last = gimple_assign_lhs (g);
|
|
g = gimple_build_assign (make_ssa_name (pointer_sized_int_node),
|
|
PLUS_EXPR, base_addr, last);
|
|
gimple_set_location (g, loc);
|
|
gsi_insert_after (&gsi, g, GSI_NEW_STMT);
|
|
tree base_end_addr = gimple_assign_lhs (g);
|
|
|
|
tree shadow = build_shadow_mem_access (&gsi, loc, base_end_addr,
|
|
shadow_ptr_type);
|
|
gimple *shadow_test = build_assign (NE_EXPR, shadow, 0);
|
|
gimple_seq seq = NULL;
|
|
gimple_seq_add_stmt (&seq, shadow_test);
|
|
gimple_seq_add_stmt (&seq, build_assign (BIT_AND_EXPR,
|
|
base_end_addr, 7));
|
|
gimple_seq_add_stmt (&seq, build_type_cast (shadow_type,
|
|
gimple_seq_last (seq)));
|
|
gimple_seq_add_stmt (&seq, build_assign (GE_EXPR,
|
|
gimple_seq_last (seq),
|
|
shadow));
|
|
gimple_seq_add_stmt (&seq, build_assign (BIT_AND_EXPR, shadow_test,
|
|
gimple_seq_last (seq)));
|
|
gimple_seq_add_stmt (&seq, build_assign (BIT_IOR_EXPR, t,
|
|
gimple_seq_last (seq)));
|
|
t = gimple_assign_lhs (gimple_seq_last (seq));
|
|
gimple_seq_set_location (seq, loc);
|
|
gsi_insert_seq_after (&gsi, seq, GSI_CONTINUE_LINKING);
|
|
}
|
|
}
|
|
|
|
g = gimple_build_cond (NE_EXPR, t, build_int_cst (TREE_TYPE (t), 0),
|
|
NULL_TREE, NULL_TREE);
|
|
gimple_set_location (g, loc);
|
|
gsi_insert_after (&gsi, g, GSI_NEW_STMT);
|
|
|
|
/* Generate call to the run-time library (e.g. __asan_report_load8). */
|
|
gsi = gsi_start_bb (then_bb);
|
|
int nargs;
|
|
tree fun = report_error_func (is_store, recover_p, size_in_bytes, &nargs);
|
|
g = gimple_build_call (fun, nargs, base_addr, len);
|
|
gimple_set_location (g, loc);
|
|
gsi_insert_after (&gsi, g, GSI_NEW_STMT);
|
|
|
|
gsi_remove (iter, true);
|
|
*iter = gsi_start_bb (else_bb);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Create ASAN shadow variable for a VAR_DECL which has been rewritten
|
|
into SSA. Already seen VAR_DECLs are stored in SHADOW_VARS_MAPPING. */
|
|
|
|
static tree
|
|
create_asan_shadow_var (tree var_decl,
|
|
hash_map<tree, tree> &shadow_vars_mapping)
|
|
{
|
|
tree *slot = shadow_vars_mapping.get (var_decl);
|
|
if (slot == NULL)
|
|
{
|
|
tree shadow_var = copy_node (var_decl);
|
|
|
|
copy_body_data id;
|
|
memset (&id, 0, sizeof (copy_body_data));
|
|
id.src_fn = id.dst_fn = current_function_decl;
|
|
copy_decl_for_dup_finish (&id, var_decl, shadow_var);
|
|
|
|
DECL_ARTIFICIAL (shadow_var) = 1;
|
|
DECL_IGNORED_P (shadow_var) = 1;
|
|
DECL_SEEN_IN_BIND_EXPR_P (shadow_var) = 0;
|
|
gimple_add_tmp_var (shadow_var);
|
|
|
|
shadow_vars_mapping.put (var_decl, shadow_var);
|
|
return shadow_var;
|
|
}
|
|
else
|
|
return *slot;
|
|
}
|
|
|
|
/* Expand ASAN_POISON ifn. */
|
|
|
|
bool
|
|
asan_expand_poison_ifn (gimple_stmt_iterator *iter,
|
|
bool *need_commit_edge_insert,
|
|
hash_map<tree, tree> &shadow_vars_mapping)
|
|
{
|
|
gimple *g = gsi_stmt (*iter);
|
|
tree poisoned_var = gimple_call_lhs (g);
|
|
if (!poisoned_var || has_zero_uses (poisoned_var))
|
|
{
|
|
gsi_remove (iter, true);
|
|
return true;
|
|
}
|
|
|
|
if (SSA_NAME_VAR (poisoned_var) == NULL_TREE)
|
|
SET_SSA_NAME_VAR_OR_IDENTIFIER (poisoned_var,
|
|
create_tmp_var (TREE_TYPE (poisoned_var)));
|
|
|
|
tree shadow_var = create_asan_shadow_var (SSA_NAME_VAR (poisoned_var),
|
|
shadow_vars_mapping);
|
|
|
|
bool recover_p;
|
|
if (flag_sanitize & SANITIZE_USER_ADDRESS)
|
|
recover_p = (flag_sanitize_recover & SANITIZE_USER_ADDRESS) != 0;
|
|
else
|
|
recover_p = (flag_sanitize_recover & SANITIZE_KERNEL_ADDRESS) != 0;
|
|
tree size = DECL_SIZE_UNIT (shadow_var);
|
|
gimple *poison_call
|
|
= gimple_build_call_internal (IFN_ASAN_MARK, 3,
|
|
build_int_cst (integer_type_node,
|
|
ASAN_MARK_POISON),
|
|
build_fold_addr_expr (shadow_var), size);
|
|
|
|
gimple *use;
|
|
imm_use_iterator imm_iter;
|
|
FOR_EACH_IMM_USE_STMT (use, imm_iter, poisoned_var)
|
|
{
|
|
if (is_gimple_debug (use))
|
|
continue;
|
|
|
|
int nargs;
|
|
bool store_p = gimple_call_internal_p (use, IFN_ASAN_POISON_USE);
|
|
tree fun = report_error_func (store_p, recover_p, tree_to_uhwi (size),
|
|
&nargs);
|
|
|
|
gcall *call = gimple_build_call (fun, 1,
|
|
build_fold_addr_expr (shadow_var));
|
|
gimple_set_location (call, gimple_location (use));
|
|
gimple *call_to_insert = call;
|
|
|
|
/* The USE can be a gimple PHI node. If so, insert the call on
|
|
all edges leading to the PHI node. */
|
|
if (is_a <gphi *> (use))
|
|
{
|
|
gphi *phi = dyn_cast<gphi *> (use);
|
|
for (unsigned i = 0; i < gimple_phi_num_args (phi); ++i)
|
|
if (gimple_phi_arg_def (phi, i) == poisoned_var)
|
|
{
|
|
edge e = gimple_phi_arg_edge (phi, i);
|
|
|
|
if (call_to_insert == NULL)
|
|
call_to_insert = gimple_copy (call);
|
|
|
|
gsi_insert_seq_on_edge (e, call_to_insert);
|
|
*need_commit_edge_insert = true;
|
|
call_to_insert = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gimple_stmt_iterator gsi = gsi_for_stmt (use);
|
|
if (store_p)
|
|
gsi_replace (&gsi, call, true);
|
|
else
|
|
gsi_insert_before (&gsi, call, GSI_NEW_STMT);
|
|
}
|
|
}
|
|
|
|
SSA_NAME_IS_DEFAULT_DEF (poisoned_var) = true;
|
|
SSA_NAME_DEF_STMT (poisoned_var) = gimple_build_nop ();
|
|
gsi_replace (iter, poison_call, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Instrument the current function. */
|
|
|
|
static unsigned int
|
|
asan_instrument (void)
|
|
{
|
|
if (shadow_ptr_types[0] == NULL_TREE)
|
|
asan_init_shadow_ptr_types ();
|
|
transform_statements ();
|
|
return 0;
|
|
}
|
|
|
|
static bool
|
|
gate_asan (void)
|
|
{
|
|
return (flag_sanitize & SANITIZE_ADDRESS) != 0
|
|
&& !lookup_attribute ("no_sanitize_address",
|
|
DECL_ATTRIBUTES (current_function_decl));
|
|
}
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_asan =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"asan", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
TV_NONE, /* tv_id */
|
|
( PROP_ssa | PROP_cfg | PROP_gimple_leh ), /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
TODO_update_ssa, /* todo_flags_finish */
|
|
};
|
|
|
|
class pass_asan : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_asan (gcc::context *ctxt)
|
|
: gimple_opt_pass (pass_data_asan, ctxt)
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
opt_pass * clone () { return new pass_asan (m_ctxt); }
|
|
virtual bool gate (function *) { return gate_asan (); }
|
|
virtual unsigned int execute (function *) { return asan_instrument (); }
|
|
|
|
}; // class pass_asan
|
|
|
|
} // anon namespace
|
|
|
|
gimple_opt_pass *
|
|
make_pass_asan (gcc::context *ctxt)
|
|
{
|
|
return new pass_asan (ctxt);
|
|
}
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_asan_O0 =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"asan0", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
TV_NONE, /* tv_id */
|
|
( PROP_ssa | PROP_cfg | PROP_gimple_leh ), /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
TODO_update_ssa, /* todo_flags_finish */
|
|
};
|
|
|
|
class pass_asan_O0 : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_asan_O0 (gcc::context *ctxt)
|
|
: gimple_opt_pass (pass_data_asan_O0, ctxt)
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
virtual bool gate (function *) { return !optimize && gate_asan (); }
|
|
virtual unsigned int execute (function *) { return asan_instrument (); }
|
|
|
|
}; // class pass_asan_O0
|
|
|
|
} // anon namespace
|
|
|
|
gimple_opt_pass *
|
|
make_pass_asan_O0 (gcc::context *ctxt)
|
|
{
|
|
return new pass_asan_O0 (ctxt);
|
|
}
|
|
|
|
#include "gt-asan.h"
|