diff mbox

[PING] Enable -fsanitize-recover for KASan

Message ID 5416B3A2.4050200@samsung.com
State New
Headers show

Commit Message

Yury Gribov Sept. 15, 2014, 9:38 a.m. UTC
On 09/05/2014 10:54 AM, Yury Gribov wrote:
> Hi all,
>
> This patch enables -fsanitize-recover for KASan by default. This causes
> KASan to continue execution after error in case of inline
> instrumentation. This feature is needed because
> - reports during early bootstrap won't even be printed
> - needed to run all tests w/o rebooting machine for every test
> - needed for interactive work on desktop
>
> Bootstrapped and regtested on x64.
>
> Ok to commit?
>
> -Y

Rebased patch to current trunk.

-Y

Comments

Jakub Jelinek Sept. 18, 2014, 10:52 a.m. UTC | #1
On Mon, Sep 15, 2014 at 01:38:42PM +0400, Yury Gribov wrote:
> --- a/gcc/builtins.def
> +++ b/gcc/builtins.def
> @@ -176,7 +176,7 @@ along with GCC; see the file COPYING3.  If not see
>    DEF_BUILTIN (ENUM, "__builtin_" NAME, BUILT_IN_NORMAL, TYPE, TYPE,    \
>  	       true, true, true, ATTRS, true, \
>  	      (flag_sanitize & (SANITIZE_ADDRESS | SANITIZE_THREAD \
> -				| SANITIZE_UNDEFINED | SANITIZE_NONDEFAULT)))
> +				| SANITIZE_UNDEFINED | SANITIZE_UNDEFINED_NONDEFAULT)))

This is too long line after the change.

> --- a/gcc/gcc.c
> +++ b/gcc/gcc.c
> @@ -8236,7 +8236,7 @@ sanitize_spec_function (int argc, const char **argv)
>    if (strcmp (argv[0], "thread") == 0)
>      return (flag_sanitize & SANITIZE_THREAD) ? "" : NULL;
>    if (strcmp (argv[0], "undefined") == 0)
> -    return ((flag_sanitize & (SANITIZE_UNDEFINED | SANITIZE_NONDEFAULT))
> +    return ((flag_sanitize & (SANITIZE_UNDEFINED | SANITIZE_UNDEFINED_NONDEFAULT))

Likewise.

> --- a/gcc/opts.c
> +++ b/gcc/opts.c
> @@ -1551,6 +1551,12 @@ common_handle_option (struct gcc_options *opts,
>  			     | SANITIZE_RETURNS_NONNULL_ATTRIBUTE))
>  	  opts->x_flag_delete_null_pointer_checks = 0;
>  
> +	/* UBSan and KASan enable recovery by default.  */
> +	opts->x_flag_sanitize_recover
> +	  = !!(flag_sanitize & (SANITIZE_UNDEFINED
> +				| SANITIZE_UNDEFINED_NONDEFAULT
> +				| SANITIZE_KERNEL_ADDRESS));
> +

Doesn't this override even user supplied -fsanitize-recover or
-fno-sanitize-recover ?  Have you tried both
-fno-sanitize-recover -fsanitize=kernel-address
and
-fsanitize=kernel-address -fno-sanitize-recover
option orders?

Seems for -fdelete-null-pointer-checks we got it wrong too,
IMHO for -fsanitize={null,{,returns-}nonnull-attribute,undefined}
we want to disable it unconditionally, regardless of whether
that option appears on the command line or not.
And we handle it right for 
-fdelete-null-pointer-checks -fsanitize=undefined
but not for
-fsanitize=undefined -fdelete-null-pointer-checks
Joseph, thoughts where to override it instead (I mean, after all
options are processed)?

In the -fsanitize-recover case, I'd on the other side think that
it should just override the default and not override explicit
user's decision.  Which could be done here, but supposedly guarded
with if (!opts_set->x_flag_sanitize_recover)?

I don't think your proposal will work properly though,
if one compiles with
-fsanitize=undefined -fsanitize=address
you'll just get userland asan with error recovery, which is highly
undesirable (not just that it changes the behavior from how it
behaved before, but especially because libasan doesn't contain
such entrypoints at all).
-fsanitize=undefined,address
or
-fsanitize=address,undefined
is normal supported mode and thus I think you either can't reuse
-fsanitize-recover option for what you want to do, or
asan.c needs to limit it to flag_sanitize & SANITIZE_KERNEL_ADDRESS
mode only.  Depends if you ever want to add recovery for userland
sanitization.

	Jakub
Yury Gribov Sept. 18, 2014, 1:15 p.m. UTC | #2
Added Marek to comment on proposed UBSan option change.

On 09/18/2014 02:52 PM, Jakub Jelinek wrote:
>> --- a/gcc/opts.c
>> +++ b/gcc/opts.c
>> @@ -1551,6 +1551,12 @@ common_handle_option (struct gcc_options *opts,
>>   			     | SANITIZE_RETURNS_NONNULL_ATTRIBUTE))
>>   	  opts->x_flag_delete_null_pointer_checks = 0;
>>
>> +	/* UBSan and KASan enable recovery by default.  */
>> +	opts->x_flag_sanitize_recover
>> +	  = !!(flag_sanitize & (SANITIZE_UNDEFINED
>> +				| SANITIZE_UNDEFINED_NONDEFAULT
>> +				| SANITIZE_KERNEL_ADDRESS));
>> +
>
> Doesn't this override even user supplied -fsanitize-recover or
> -fno-sanitize-recover ?  Have you tried both
> -fno-sanitize-recover -fsanitize=kernel-address
> and
> -fsanitize=kernel-address -fno-sanitize-recover
> option orders?

I did and this worked in a seemingly logical way:
* -fsanitize=address (disable recovery)
* -fsanitize-recover -fsanitize=address (disable recovery)
* -fsanitize=address -fsanitize-recover (enable recovery)
* -fsanitize=kernel-address (enable recovery)
* -fno-sanitize-recover -fsanitize=kernel-address (enable recovery)
* -fsanitize=kernel-address -fno-sanitize-recover (enable recovery)

> Seems for -fdelete-null-pointer-checks we got it wrong too,
> IMHO for -fsanitize={null,{,returns-}nonnull-attribute,undefined}
> we want to disable it unconditionally, regardless of whether
> that option appears on the command line or not.

My understanding is that all 
-fsanitize=(address|kernel-address|undefined|you-name-it) are simply 
packs of options to enable. User may override any selected option from 
the pack if he so desires.

> I don't think your proposal will work properly though,
> if one compiles with
> -fsanitize=undefined -fsanitize=address
> you'll just get userland asan with error recovery, which is highly
> undesirable

Now that's a problem. Looks like I'll need a separate flag to achieve 
what I need (-fasan-recover? And maybe then rename -fsanitize-recover to 
-fubsan-recover for consistency?).

> or asan.c needs to limit it to flag_sanitize & SANITIZE_KERNEL_ADDRESS
> mode only.

We may want to UBsanitize kernel in future and this may cause the same 
problem as for userspace Asan/UBSan interaction you described above.

 > Depends if you ever want to add recovery for userland
 > sanitization.

Also kernel developers want both recoverable (more user-friendly) and 
non-recoverable (faster) Asan error handling.

-Y
Joseph Myers Sept. 18, 2014, 11:47 p.m. UTC | #3
On Thu, 18 Sep 2014, Jakub Jelinek wrote:

> Seems for -fdelete-null-pointer-checks we got it wrong too,
> IMHO for -fsanitize={null,{,returns-}nonnull-attribute,undefined}
> we want to disable it unconditionally, regardless of whether
> that option appears on the command line or not.
> And we handle it right for 
> -fdelete-null-pointer-checks -fsanitize=undefined
> but not for
> -fsanitize=undefined -fdelete-null-pointer-checks
> Joseph, thoughts where to override it instead (I mean, after all
> options are processed)?

finish_options is the obvious place to do that.
diff mbox

Patch

2014-09-15  Yury Gribov  <y.gribov@samsung.com>

gcc/
	* asan.c (report_error_func): Optionally call recoverable
	routines.
	(asan_expand_check_ifn): Likewise.
	(check_func): Fix formatting.
	* common.opt (fsanitize-recover): Disable by default.
	* sanitizer.def: New builtins.
	* opts.c (common_handle_option): Enable flag_sanitize_recover
	for UBSan and KASan by default.
	* flag-types.h (SANITIZE_UNDEFINED_NONDEFAULT): Rename.
	* gcc.c (sanitize_spec_function): Likewise.
	* opts.c (common_handle_option): Likewise.

gcc/testsuite/
	* c-c++-common/asan/recovery-1.c: New test.
	* c-c++-common/asan/recovery-2.c: New test.
	* c-c++-common/asan/recovery-common.inc: New file.

commit 080184e321b49641cd1e3bab2615eaf82164c683
Author: Yury Gribov <y.gribov@samsung.com>
Date:   Fri Aug 29 16:43:42 2014 +0400

    2014-09-04  Yury Gribov  <y.gribov@samsung.com>
    
    gcc/
    	* asan.c (report_error_func): Optionally call recoverable
    	routines.
    	(asan_expand_check_ifn): Likewise.
    	(check_func): Fix formatting.
    	* common.opt (fsanitize-recover): Disable by default.
    	* sanitizer.def: New builtins.
    	* opts.c (common_handle_option): Enable flag_sanitize_recover
    	for UBSan and KASan by default.
    	* flag-types.h (SANITIZE_UNDEFINED_NONDEFAULT): Rename.
    	* gcc.c (sanitize_spec_function): Likewise.
    	* opts.c (common_handle_option): Likewise.
    
    gcc/testsuite/
    	* c-c++-common/asan/recovery-1.c: New test.
    	* c-c++-common/asan/recovery-2.c: New test.
    	* c-c++-common/asan/recovery-common.inc: New file.

diff --git a/gcc/asan.c b/gcc/asan.c
index e6820ea..1d0a26a 100644
--- a/gcc/asan.c
+++ b/gcc/asan.c
@@ -1373,22 +1373,36 @@  asan_protect_global (tree decl)
    IS_STORE is either 1 (for a store) or 0 (for a load).  */
 
 static tree
-report_error_func (bool is_store, HOST_WIDE_INT size_in_bytes, int *nargs)
-{
-  static enum built_in_function report[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 } };
+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_RECOVER_LOAD1,
+	    BUILT_IN_ASAN_REPORT_RECOVER_LOAD2,
+	    BUILT_IN_ASAN_REPORT_RECOVER_LOAD4,
+	    BUILT_IN_ASAN_REPORT_RECOVER_LOAD8,
+	    BUILT_IN_ASAN_REPORT_RECOVER_LOAD16,
+	    BUILT_IN_ASAN_REPORT_RECOVER_LOAD_N },
+	  { BUILT_IN_ASAN_REPORT_RECOVER_STORE1,
+	    BUILT_IN_ASAN_REPORT_RECOVER_STORE2,
+	    BUILT_IN_ASAN_REPORT_RECOVER_STORE4,
+	    BUILT_IN_ASAN_REPORT_RECOVER_STORE8,
+	    BUILT_IN_ASAN_REPORT_RECOVER_STORE16,
+	    BUILT_IN_ASAN_REPORT_RECOVER_STORE_N } } };
   if (size_in_bytes == -1)
     {
       *nargs = 2;
-      return builtin_decl_implicit (report[is_store][5]);
+      return builtin_decl_implicit (report[recover_p][is_store][5]);
     }
   *nargs = 1;
-  return builtin_decl_implicit (report[is_store][exact_log2 (size_in_bytes)]);
+  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}.
@@ -1399,11 +1413,11 @@  check_func (bool is_store, int size_in_bytes, int *nargs)
 {
   static enum built_in_function check[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_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_STORE4, BUILT_IN_ASAN_STORE8,
+	  BUILT_IN_ASAN_STORE16, BUILT_IN_ASAN_STOREN } };
   if (size_in_bytes == -1)
     {
       *nargs = 2;
@@ -2574,9 +2588,10 @@  asan_expand_check_ifn (gimple_stmt_iterator *iter, bool use_calls)
   /* Get an iterator on the point where we can add the condition
      statement for the instrumentation.  */
   basic_block then_bb, else_bb;
+  bool create_then_fallthru_edge = flag_sanitize_recover != 0;
   gsi = create_cond_insert_point (&gsi, /*before_p*/false,
 				  /*then_more_likely_p=*/false,
-				  /*create_then_fallthru_edge=*/false,
+				  create_then_fallthru_edge,
 				  &then_bb,
 				  &else_bb);
 
@@ -2675,7 +2690,9 @@  asan_expand_check_ifn (gimple_stmt_iterator *iter, bool use_calls)
   /* 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, size_in_bytes, &nargs);
+  tree fun = report_error_func (is_store,
+				/*recover_p*/flag_sanitize_recover != 0,
+				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);
diff --git a/gcc/builtins.def b/gcc/builtins.def
index cd823a3..261373c 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -176,7 +176,7 @@  along with GCC; see the file COPYING3.  If not see
   DEF_BUILTIN (ENUM, "__builtin_" NAME, BUILT_IN_NORMAL, TYPE, TYPE,    \
 	       true, true, true, ATTRS, true, \
 	      (flag_sanitize & (SANITIZE_ADDRESS | SANITIZE_THREAD \
-				| SANITIZE_UNDEFINED | SANITIZE_NONDEFAULT)))
+				| SANITIZE_UNDEFINED | SANITIZE_UNDEFINED_NONDEFAULT)))
 
 #undef DEF_CILKPLUS_BUILTIN
 #define DEF_CILKPLUS_BUILTIN(ENUM, NAME, TYPE, ATTRS)  \
diff --git a/gcc/common.opt b/gcc/common.opt
index 14b9d78..489a21e 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -884,8 +884,8 @@  Common Joined RejectNegative Var(common_deferred_options) Defer
 -fasan-shadow-offset=<string>	Use custom shadow memory offset.
 
 fsanitize-recover
-Common Report Var(flag_sanitize_recover) Init(1)
-After diagnosing undefined behavior attempt to continue execution
+Common Report Var(flag_sanitize_recover)
+After diagnosing error attempt to continue execution
 
 fsanitize-undefined-trap-on-error
 Common Report Var(flag_sanitize_undefined_trap_on_error) Init(0)
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index d0818e5..c9a6e17 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -242,7 +242,7 @@  enum sanitize_code {
 		       | SANITIZE_BOUNDS | SANITIZE_ALIGNMENT
 		       | SANITIZE_NONNULL_ATTRIBUTE
 		       | SANITIZE_RETURNS_NONNULL_ATTRIBUTE,
-  SANITIZE_NONDEFAULT = SANITIZE_FLOAT_DIVIDE | SANITIZE_FLOAT_CAST
+  SANITIZE_UNDEFINED_NONDEFAULT = SANITIZE_FLOAT_DIVIDE | SANITIZE_FLOAT_CAST
 };
 
 /* flag_vtable_verify initialization levels. */
diff --git a/gcc/gcc.c b/gcc/gcc.c
index c550d9d..b2031ed 100644
--- a/gcc/gcc.c
+++ b/gcc/gcc.c
@@ -8236,7 +8236,7 @@  sanitize_spec_function (int argc, const char **argv)
   if (strcmp (argv[0], "thread") == 0)
     return (flag_sanitize & SANITIZE_THREAD) ? "" : NULL;
   if (strcmp (argv[0], "undefined") == 0)
-    return ((flag_sanitize & (SANITIZE_UNDEFINED | SANITIZE_NONDEFAULT))
+    return ((flag_sanitize & (SANITIZE_UNDEFINED | SANITIZE_UNDEFINED_NONDEFAULT))
 	    && !flag_sanitize_undefined_trap_on_error) ? "" : NULL;
   if (strcmp (argv[0], "leak") == 0)
     return ((flag_sanitize
diff --git a/gcc/opts.c b/gcc/opts.c
index 774a599..cd00a88 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -1551,6 +1551,12 @@  common_handle_option (struct gcc_options *opts,
 			     | SANITIZE_RETURNS_NONNULL_ATTRIBUTE))
 	  opts->x_flag_delete_null_pointer_checks = 0;
 
+	/* UBSan and KASan enable recovery by default.  */
+	opts->x_flag_sanitize_recover
+	  = !!(flag_sanitize & (SANITIZE_UNDEFINED
+				| SANITIZE_UNDEFINED_NONDEFAULT
+				| SANITIZE_KERNEL_ADDRESS));
+
 	/* Kernel ASan implies normal ASan but does not yet support
 	   all features.  */
 	if (flag_sanitize & SANITIZE_KERNEL_ADDRESS)
diff --git a/gcc/sanitizer.def b/gcc/sanitizer.def
index bba28bd..c61fd25 100644
--- a/gcc/sanitizer.def
+++ b/gcc/sanitizer.def
@@ -57,6 +57,44 @@  DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_STORE16, "__asan_report_store16",
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_STORE_N, "__asan_report_store_n",
 		      BT_FN_VOID_PTR_PTRMODE,
 		      ATTR_TMPURE_NORETURN_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_RECOVER_LOAD1,
+		      "__asan_report_recover_load1",
+		      BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_RECOVER_LOAD2,
+		      "__asan_report_recover_load2",
+		      BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_RECOVER_LOAD4,
+		      "__asan_report_recover_load4",
+		      BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_RECOVER_LOAD8,
+		      "__asan_report_recover_load8",
+		      BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_RECOVER_LOAD16,
+		      "__asan_report_recover_load16",
+		      BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_RECOVER_LOAD_N,
+		      "__asan_report_recover_load_n",
+		      BT_FN_VOID_PTR_PTRMODE,
+		      ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_RECOVER_STORE1,
+		      "__asan_report_recover_store1",
+		      BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_RECOVER_STORE2,
+		      "__asan_report_recover_store2",
+		      BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_RECOVER_STORE4,
+		      "__asan_report_recover_store4",
+		      BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_RECOVER_STORE8,
+		      "__asan_report_recover_store8",
+		      BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_RECOVER_STORE16,
+		      "__asan_report_recover_store16",
+		      BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
+DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_REPORT_RECOVER_STORE_N,
+		      "__asan_report_recover_store_n",
+		      BT_FN_VOID_PTR_PTRMODE,
+		      ATTR_TMPURE_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_LOAD1, "__asan_load1",
 		      BT_FN_VOID_PTR, ATTR_TMPURE_NOTHROW_LEAF_LIST)
 DEF_SANITIZER_BUILTIN(BUILT_IN_ASAN_LOAD2, "__asan_load2",
diff --git a/gcc/testsuite/c-c++-common/asan/recovery-1.c b/gcc/testsuite/c-c++-common/asan/recovery-1.c
new file mode 100644
index 0000000..ac3d5a7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/recovery-1.c
@@ -0,0 +1,8 @@ 
+/* Check that -fsanitize-recover is disabled for ASan by default.  */
+
+/* { dg-do run } */
+/* { dg-shouldfail "asan" } */
+
+#include "recovery-common.inc"
+
+/* { dg-output "ERROR: AddressSanitizer:\[^\n\r]*on address\[^\n\r]*" } */
diff --git a/gcc/testsuite/c-c++-common/asan/recovery-2.c b/gcc/testsuite/c-c++-common/asan/recovery-2.c
new file mode 100644
index 0000000..001eb0a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/recovery-2.c
@@ -0,0 +1,6 @@ 
+/* Check that -fsanitize-recover works for ASan.  */
+
+/* { dg-do run } */
+/* { dg-options "-fsanitize-recover" } */
+
+#include "recovery-common.inc"
diff --git a/gcc/testsuite/c-c++-common/asan/recovery-common.inc b/gcc/testsuite/c-c++-common/asan/recovery-common.inc
new file mode 100644
index 0000000..43f0e10
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/asan/recovery-common.inc
@@ -0,0 +1,31 @@ 
+#include <sanitizer/asan_interface.h>
+
+#define NREPORTS 5
+
+static int counter = NREPORTS;
+
+#ifdef __cplusplus
+extern "C"
+#endif
+void __attribute__((used))
+__asan_report_recover_store4 (void *p)
+{
+  --counter;
+}
+ 
+void __attribute__((noinline, noclone))
+foo (int *p)
+{
+  *p = 0;
+}
+
+int main ()
+{
+  int x, i;
+  __asan_poison_memory_region (&x, sizeof (x));
+  for (i = 0; i < NREPORTS; ++i)
+    foo (&x);
+  if (counter != 0)
+    __builtin_abort ();
+  return 0;
+}