re PR go/60406 (recover.go: test13reflect2 test failure)
PR go/60406 runtime: Check callers in can_recover if return address doesn't match. Also use __builtin_extract_return_address and tighten up the checks in FFI code. Fixes PR 60406. From-SVN: r216003
This commit is contained in:
parent
9d07d890e6
commit
19d4baed57
|
@ -36,21 +36,23 @@ void ffiFree(void *data)
|
||||||
Go callback function (passed in user_data) with the pointer to the
|
Go callback function (passed in user_data) with the pointer to the
|
||||||
arguments and the results area. */
|
arguments and the results area. */
|
||||||
|
|
||||||
|
static void ffi_callback (ffi_cif *, void *, void **, void *)
|
||||||
|
__asm__ ("reflect.ffi_callback");
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ffi_callback (ffi_cif* cif __attribute__ ((unused)), void *results,
|
ffi_callback (ffi_cif* cif __attribute__ ((unused)), void *results,
|
||||||
void **args, void *user_data)
|
void **args, void *user_data)
|
||||||
{
|
{
|
||||||
Location locs[6];
|
Location locs[8];
|
||||||
int n;
|
int n;
|
||||||
int i;
|
int i;
|
||||||
const void *pc;
|
|
||||||
FuncVal *fv;
|
FuncVal *fv;
|
||||||
void (*f) (void *, void *);
|
void (*f) (void *, void *);
|
||||||
|
|
||||||
/* This function is called from some series of FFI closure functions
|
/* This function is called from some series of FFI closure functions
|
||||||
called by a Go function. We want to pass the PC of the Go
|
called by a Go function. We want to see whether the caller of
|
||||||
function to makefunc_can_recover. Look up the stack for a
|
the closure functions can recover. Look up the stack and skip
|
||||||
function that is definitely not an FFI function. */
|
the FFI functions. */
|
||||||
n = runtime_callers (1, &locs[0], sizeof locs / sizeof locs[0], true);
|
n = runtime_callers (1, &locs[0], sizeof locs / sizeof locs[0], true);
|
||||||
for (i = 0; i < n; i++)
|
for (i = 0; i < n; i++)
|
||||||
{
|
{
|
||||||
|
@ -61,28 +63,19 @@ ffi_callback (ffi_cif* cif __attribute__ ((unused)), void *results,
|
||||||
if (locs[i].function.len < 4)
|
if (locs[i].function.len < 4)
|
||||||
break;
|
break;
|
||||||
name = locs[i].function.str;
|
name = locs[i].function.str;
|
||||||
if (*name == '_')
|
|
||||||
{
|
|
||||||
if (locs[i].function.len < 5)
|
|
||||||
break;
|
|
||||||
++name;
|
|
||||||
}
|
|
||||||
if (name[0] != 'f' || name[1] != 'f' || name[2] != 'i' || name[3] != '_')
|
if (name[0] != 'f' || name[1] != 'f' || name[2] != 'i' || name[3] != '_')
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (i < n)
|
if (i < n)
|
||||||
pc = (const void *) locs[i].pc;
|
__go_makefunc_ffi_can_recover (locs + i, n - i);
|
||||||
else
|
|
||||||
pc = __builtin_return_address (0);
|
|
||||||
|
|
||||||
__go_makefunc_can_recover (pc);
|
|
||||||
|
|
||||||
fv = (FuncVal *) user_data;
|
fv = (FuncVal *) user_data;
|
||||||
__go_set_closure (fv);
|
__go_set_closure (fv);
|
||||||
f = (void *) fv->fn;
|
f = (void *) fv->fn;
|
||||||
f (args, results);
|
f (args, results);
|
||||||
|
|
||||||
__go_makefunc_returning ();
|
if (i < n)
|
||||||
|
__go_makefunc_returning ();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Allocate an FFI closure and arrange to call ffi_callback. */
|
/* Allocate an FFI closure and arrange to call ffi_callback. */
|
||||||
|
|
|
@ -80,6 +80,6 @@ __go_set_defer_retaddr (void *retaddr)
|
||||||
|
|
||||||
g = runtime_g ();
|
g = runtime_g ();
|
||||||
if (g->defer != NULL)
|
if (g->defer != NULL)
|
||||||
g->defer->__retaddr = retaddr;
|
g->defer->__retaddr = __builtin_extract_return_addr (retaddr);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,12 @@ extern void __go_print_string (struct String);
|
||||||
|
|
||||||
extern struct __go_empty_interface __go_recover (void);
|
extern struct __go_empty_interface __go_recover (void);
|
||||||
|
|
||||||
extern _Bool __go_can_recover (const void *);
|
extern _Bool __go_can_recover (void *);
|
||||||
|
|
||||||
extern void __go_makefunc_can_recover (const void *retaddr);
|
extern void __go_makefunc_can_recover (void *retaddr);
|
||||||
|
|
||||||
|
struct Location;
|
||||||
|
extern void __go_makefunc_ffi_can_recover (struct Location *, int);
|
||||||
|
|
||||||
extern void __go_makefunc_returning (void);
|
extern void __go_makefunc_returning (void);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,36 @@
|
||||||
#include "go-panic.h"
|
#include "go-panic.h"
|
||||||
#include "go-defer.h"
|
#include "go-defer.h"
|
||||||
|
|
||||||
|
/* If the top of the defer stack can be recovered, then return it.
|
||||||
|
Otherwise return NULL. */
|
||||||
|
|
||||||
|
static struct __go_defer_stack *
|
||||||
|
current_defer ()
|
||||||
|
{
|
||||||
|
G *g;
|
||||||
|
struct __go_defer_stack *d;
|
||||||
|
|
||||||
|
g = runtime_g ();
|
||||||
|
|
||||||
|
d = g->defer;
|
||||||
|
if (d == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* The panic which would be recovered is the one on the top of the
|
||||||
|
panic stack. We do not want to recover it if that panic was on
|
||||||
|
the top of the panic stack when this function was deferred. */
|
||||||
|
if (d->__panic == g->panic)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* The deferred thunk will call _go_set_defer_retaddr. If this has
|
||||||
|
not happened, then we have not been called via defer, and we can
|
||||||
|
not recover. */
|
||||||
|
if (d->__retaddr == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
/* This is called by a thunk to see if the real function should be
|
/* This is called by a thunk to see if the real function should be
|
||||||
permitted to recover a panic value. Recovering a value is
|
permitted to recover a panic value. Recovering a value is
|
||||||
permitted if the thunk was called directly by defer. RETADDR is
|
permitted if the thunk was called directly by defer. RETADDR is
|
||||||
|
@ -16,79 +46,126 @@
|
||||||
__go_can_recover--this is, the thunk. */
|
__go_can_recover--this is, the thunk. */
|
||||||
|
|
||||||
_Bool
|
_Bool
|
||||||
__go_can_recover (const void *retaddr)
|
__go_can_recover (void *retaddr)
|
||||||
{
|
{
|
||||||
G *g;
|
|
||||||
struct __go_defer_stack *d;
|
struct __go_defer_stack *d;
|
||||||
const char* ret;
|
const char* ret;
|
||||||
const char* dret;
|
const char* dret;
|
||||||
Location loc;
|
Location locs[16];
|
||||||
const byte *name;
|
const byte *name;
|
||||||
|
intgo len;
|
||||||
|
int n;
|
||||||
|
int i;
|
||||||
|
_Bool found_ffi_callback;
|
||||||
|
|
||||||
g = runtime_g ();
|
d = current_defer ();
|
||||||
|
|
||||||
d = g->defer;
|
|
||||||
if (d == NULL)
|
if (d == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* The panic which this function would recover is the one on the top
|
ret = (const char *) __builtin_extract_return_addr (retaddr);
|
||||||
of the panic stack. We do not want to recover it if that panic
|
|
||||||
was on the top of the panic stack when this function was
|
|
||||||
deferred. */
|
|
||||||
if (d->__panic == g->panic)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* D->__RETADDR is the address of a label immediately following the
|
|
||||||
call to the thunk. We can recover a panic if that is the same as
|
|
||||||
the return address of the thunk. We permit a bit of slack in
|
|
||||||
case there is any code between the function return and the label,
|
|
||||||
such as an instruction to adjust the stack pointer. */
|
|
||||||
|
|
||||||
ret = (const char *) retaddr;
|
|
||||||
|
|
||||||
#ifdef __sparc__
|
|
||||||
/* On SPARC the address we get, from __builtin_return_address, is
|
|
||||||
the address of the call instruction. Adjust forward, also
|
|
||||||
skipping the delayed instruction following the call. */
|
|
||||||
ret += 8;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
dret = (const char *) d->__retaddr;
|
dret = (const char *) d->__retaddr;
|
||||||
if (ret <= dret && ret + 16 >= dret)
|
if (ret <= dret && ret + 16 >= dret)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
/* On some systems, in some cases, the return address does not work
|
||||||
|
reliably. See http://gcc.gnu.org/PR60406. If we are permitted
|
||||||
|
to call recover, the call stack will look like this:
|
||||||
|
__go_panic, __go_undefer, etc.
|
||||||
|
thunk to call deferred function (calls __go_set_defer_retaddr)
|
||||||
|
function that calls __go_can_recover (passing return address)
|
||||||
|
__go_can_recover
|
||||||
|
Calling runtime_callers will skip the thunks. So if our caller's
|
||||||
|
caller starts with __go, then we are permitted to call
|
||||||
|
recover. */
|
||||||
|
|
||||||
|
if (runtime_callers (1, &locs[0], 2, false) < 2)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
name = locs[1].function.str;
|
||||||
|
len = locs[1].function.len;
|
||||||
|
|
||||||
|
/* Although locs[1].function is a Go string, we know it is
|
||||||
|
NUL-terminated. */
|
||||||
|
if (len > 4
|
||||||
|
&& __builtin_strchr ((const char *) name, '.') == NULL
|
||||||
|
&& __builtin_strncmp ((const char *) name, "__go_", 4) == 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* If we are called from __go_makefunc_can_recover, then we need to
|
||||||
|
look one level higher. */
|
||||||
|
if (locs[0].function.len > 0
|
||||||
|
&& __builtin_strcmp ((const char *) locs[0].function.str,
|
||||||
|
"__go_makefunc_can_recover") == 0)
|
||||||
|
{
|
||||||
|
if (runtime_callers (3, &locs[0], 1, false) < 1)
|
||||||
|
return 0;
|
||||||
|
name = locs[0].function.str;
|
||||||
|
len = locs[0].function.len;
|
||||||
|
if (len > 4
|
||||||
|
&& __builtin_strchr ((const char *) name, '.') == NULL
|
||||||
|
&& __builtin_strncmp ((const char *) name, "__go_", 4) == 0)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* If the function calling recover was created by reflect.MakeFunc,
|
/* If the function calling recover was created by reflect.MakeFunc,
|
||||||
then RETADDR will be somewhere in libffi. Our caller is
|
then __go_makefunc_can_recover or __go_makefunc_ffi_can_recover
|
||||||
permitted to recover if it was called from libffi. */
|
will have set the __makefunc_can_recover field. */
|
||||||
if (!d->__makefunc_can_recover)
|
if (!d->__makefunc_can_recover)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (runtime_callers (2, &loc, 1, false) < 1)
|
/* We look up the stack, ignoring libffi functions and functions in
|
||||||
return 0;
|
the reflect package, until we find reflect.makeFuncStub or
|
||||||
|
reflect.ffi_callback called by FFI functions. Then we check the
|
||||||
|
caller of that function. */
|
||||||
|
|
||||||
/* If we have no function name, then we weren't called by Go code.
|
n = runtime_callers (2, &locs[0], sizeof locs / sizeof locs[0], false);
|
||||||
Guess that we were called by libffi. */
|
found_ffi_callback = 0;
|
||||||
if (loc.function.len == 0)
|
for (i = 0; i < n; i++)
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (loc.function.len < 4)
|
|
||||||
return 0;
|
|
||||||
name = loc.function.str;
|
|
||||||
if (*name == '_')
|
|
||||||
{
|
{
|
||||||
if (loc.function.len < 5)
|
const byte *name;
|
||||||
return 0;
|
|
||||||
++name;
|
if (locs[i].function.len == 0)
|
||||||
|
{
|
||||||
|
/* No function name means this caller isn't Go code. Assume
|
||||||
|
that this is libffi. */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ignore functions in libffi. */
|
||||||
|
name = locs[i].function.str;
|
||||||
|
if (__builtin_strncmp ((const char *) name, "ffi_", 4) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (found_ffi_callback)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (__builtin_strcmp ((const char *) name, "reflect.ffi_callback") == 0)
|
||||||
|
{
|
||||||
|
found_ffi_callback = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__builtin_strcmp ((const char *) name, "reflect.makeFuncStub") == 0)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ignore other functions in the reflect package. */
|
||||||
|
if (__builtin_strncmp ((const char *) name, "reflect.", 8) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* We should now be looking at the real caller. */
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name[0] == 'f' && name[1] == 'f' && name[2] == 'i' && name[3] == '_')
|
if (i < n && locs[i].function.len > 0)
|
||||||
return 1;
|
{
|
||||||
|
name = locs[i].function.str;
|
||||||
/* We may also be called by reflect.makeFuncImpl.call or
|
if (__builtin_strncmp ((const char *) name, "__go_", 4) == 0)
|
||||||
reflect.ffiCall, for a function created by reflect.MakeFunc. */
|
return 1;
|
||||||
if (__builtin_strstr ((const char *) name, "makeFuncImpl") != NULL
|
}
|
||||||
|| __builtin_strcmp ((const char *) name, "reflect.ffiCall") == 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -99,14 +176,58 @@ __go_can_recover (const void *retaddr)
|
||||||
real MakeFunc function is permitted to call recover. */
|
real MakeFunc function is permitted to call recover. */
|
||||||
|
|
||||||
void
|
void
|
||||||
__go_makefunc_can_recover (const void *retaddr)
|
__go_makefunc_can_recover (void *retaddr)
|
||||||
{
|
{
|
||||||
struct __go_defer_stack *d;
|
struct __go_defer_stack *d;
|
||||||
|
|
||||||
d = runtime_g ()->defer;
|
d = current_defer ();
|
||||||
if (d != NULL
|
if (d == NULL)
|
||||||
&& !d->__makefunc_can_recover
|
return;
|
||||||
&& __go_can_recover (retaddr))
|
|
||||||
|
/* If we are already in a call stack of MakeFunc functions, there is
|
||||||
|
nothing we can usefully check here. */
|
||||||
|
if (d->__makefunc_can_recover)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (__go_can_recover (retaddr))
|
||||||
|
d->__makefunc_can_recover = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This function is called when code is about to enter a function
|
||||||
|
created by the libffi version of reflect.MakeFunc. This function
|
||||||
|
is passed the names of the callers of the libffi code that called
|
||||||
|
the stub. It uses to decide whether it is permitted to call
|
||||||
|
recover, and sets d->__makefunc_can_recover so that __go_recover
|
||||||
|
can make the same decision. */
|
||||||
|
|
||||||
|
void
|
||||||
|
__go_makefunc_ffi_can_recover (struct Location *loc, int n)
|
||||||
|
{
|
||||||
|
struct __go_defer_stack *d;
|
||||||
|
const byte *name;
|
||||||
|
intgo len;
|
||||||
|
|
||||||
|
d = current_defer ();
|
||||||
|
if (d == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* If we are already in a call stack of MakeFunc functions, there is
|
||||||
|
nothing we can usefully check here. */
|
||||||
|
if (d->__makefunc_can_recover)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* LOC points to the caller of our caller. That will be a thunk.
|
||||||
|
If its caller was a runtime function, then it was called directly
|
||||||
|
by defer. */
|
||||||
|
|
||||||
|
if (n < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
name = (loc + 1)->function.str;
|
||||||
|
len = (loc + 1)->function.len;
|
||||||
|
if (len > 4
|
||||||
|
&& __builtin_strchr ((const char *) name, '.') == NULL
|
||||||
|
&& __builtin_strncmp ((const char *) name, "__go_", 4) == 0)
|
||||||
d->__makefunc_can_recover = 1;
|
d->__makefunc_can_recover = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,10 @@ runtime_freedefer(Defer *d)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run all deferred functions for the current goroutine.
|
// Run all deferred functions for the current goroutine.
|
||||||
|
// This is noinline for go_can_recover.
|
||||||
|
static void __go_rundefer (void) __attribute__ ((noinline));
|
||||||
static void
|
static void
|
||||||
rundefer(void)
|
__go_rundefer(void)
|
||||||
{
|
{
|
||||||
G *g;
|
G *g;
|
||||||
Defer *d;
|
Defer *d;
|
||||||
|
@ -219,7 +221,7 @@ void runtime_Goexit (void) __asm__ (GOSYM_PREFIX "runtime.Goexit");
|
||||||
void
|
void
|
||||||
runtime_Goexit(void)
|
runtime_Goexit(void)
|
||||||
{
|
{
|
||||||
rundefer();
|
__go_rundefer();
|
||||||
runtime_goexit();
|
runtime_goexit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue