Ian Lance Taylor
2014-10-08 14:03:15 UTC
This patch to libgo fixes PR 60406, in which a problem arises when
passing a large argument to a deferred function. The current code
generates a thunk that looks like this:
if (__go_set_defer_retaddr (&&L))
goto L;
deferred_function(args);
L:
Then the deferred_function calls
__go_can_recover (__builtin_return_address())
and the code in libgo compares the address passed to __go_can_recover to
the address passed to __go_set_defer_retaddr. This rather baroque
approach is so that a defer of a variable of function type will work
correctly.
This code normally works fine. However, if args is very large, then it
will cause GCC to generate a block copy. And on systems where that
block copy introduces a loop into the code, the exact location of L may
be split to a different block so that it no longer immediately follows
the call to deferred_function. In that case the comparison in
__go_can_recover will fail, and the call to recover will fail when it
should succeed.
This is an unusual case. Most calls to defer pass no arguments to the
deferred function. It's quite unusual to pass a large argument.
However, we should of course handle it correctly.
This patch does this by adding new code when the return address fails to
match. We fetch the callers to see whether we are being directly
invoked as a deferred function. We detect this case by the simple
approach of seeing whether the caller starts with "__go_". The patch
also adjusts one case that calls a deferred function to start with __go_
to match this. This is a heuristic that is safe at present and be made
more safe in the future.
This patch also tightens up the checks for deferring a function created
by reflect.MakeFunc, to make that case more reliable.
This patch also introduces uses of __builtin_extract_return_addr for
systems that need it, which permits us to remove a #ifdef __sparc__.
Bootstrapped and ran Go testsuite on x86_64-unknown-linux-gnu and
powerpc64le-unnown-linux-gnu. Tested by others on Alpha and S/390.
Thanks to Dominik Vogt for forcing the issue and examining the
underlying assumptions.
Committed to mainline. Although this fixes a bug this seems to me to be
too risky for the 4.9 branch, especially given that the bug has only
ever been seen in the testsuite, and is extremely unlikely to arise in
ordinary user code.
Ian
passing a large argument to a deferred function. The current code
generates a thunk that looks like this:
if (__go_set_defer_retaddr (&&L))
goto L;
deferred_function(args);
L:
Then the deferred_function calls
__go_can_recover (__builtin_return_address())
and the code in libgo compares the address passed to __go_can_recover to
the address passed to __go_set_defer_retaddr. This rather baroque
approach is so that a defer of a variable of function type will work
correctly.
This code normally works fine. However, if args is very large, then it
will cause GCC to generate a block copy. And on systems where that
block copy introduces a loop into the code, the exact location of L may
be split to a different block so that it no longer immediately follows
the call to deferred_function. In that case the comparison in
__go_can_recover will fail, and the call to recover will fail when it
should succeed.
This is an unusual case. Most calls to defer pass no arguments to the
deferred function. It's quite unusual to pass a large argument.
However, we should of course handle it correctly.
This patch does this by adding new code when the return address fails to
match. We fetch the callers to see whether we are being directly
invoked as a deferred function. We detect this case by the simple
approach of seeing whether the caller starts with "__go_". The patch
also adjusts one case that calls a deferred function to start with __go_
to match this. This is a heuristic that is safe at present and be made
more safe in the future.
This patch also tightens up the checks for deferring a function created
by reflect.MakeFunc, to make that case more reliable.
This patch also introduces uses of __builtin_extract_return_addr for
systems that need it, which permits us to remove a #ifdef __sparc__.
Bootstrapped and ran Go testsuite on x86_64-unknown-linux-gnu and
powerpc64le-unnown-linux-gnu. Tested by others on Alpha and S/390.
Thanks to Dominik Vogt for forcing the issue and examining the
underlying assumptions.
Committed to mainline. Although this fixes a bug this seems to me to be
too risky for the 4.9 branch, especially given that the bug has only
ever been seen in the testsuite, and is extremely unlikely to arise in
ordinary user code.
Ian