-
-
Notifications
You must be signed in to change notification settings - Fork 606
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
signals: proper support for sigaltstack() and SA_ONSTACK sigaction flag
When an application gets a SIGSEGV because it overflowed the thread's stack, it is pointless to try to run a signal handler on the same stack which has no room to run on. This is why Linux provides a sigaltstack() function which allows the user to specify a (per-thread) stack for running signal handlers, and a SA_ONSTACK flag for sigaction() to ask to use the alternative stack for a specific handler. So far these features were stubbed, and this patch provides a complete implementation, and also a test - which overflows the stack and tries to recover with a signal handler (without SA_ONSTACK properly working, the test crashes when it tries to run the signal handler). Fixes #809. Proper support of sigaltstack() is important for golang (see issue #522). Signed-off-by: Nadav Har'El <[email protected]> Message-Id: <[email protected]>
- Loading branch information
Showing
4 changed files
with
157 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* Copyright (C) 2017 ScyllaDB, Ltd. | ||
* | ||
* This work is open source software, licensed under the terms of the | ||
* BSD license as described in the LICENSE file in the top-level directory. | ||
*/ | ||
|
||
// This test works on both Linux and OSv. | ||
// To compile on Linux, use: c++ -std=c++11 tests/tst-sigaltstack.cc | ||
|
||
#include <signal.h> | ||
#include <assert.h> | ||
#include <setjmp.h> | ||
#include <sys/mman.h> | ||
|
||
#include <iostream> | ||
|
||
static int tests = 0, fails = 0; | ||
|
||
#define expect(actual, expected) do_expect(actual, expected, #actual, #expected, __FILE__, __LINE__) | ||
template<typename T> | ||
bool do_expect(T actual, T expected, const char *actuals, const char *expecteds, const char *file, int line) | ||
{ | ||
++tests; | ||
if (actual != expected) { | ||
fails++; | ||
std::cout << "FAIL: " << file << ":" << line << ": For " << actuals << | ||
", expected " << expecteds << ", saw " << actual << ".\n"; | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
#define expect_errno(call, experrno) ( \ | ||
do_expect(call, -1, #call, "-1", __FILE__, __LINE__) && \ | ||
do_expect(errno, experrno, #call " errno", #experrno, __FILE__, __LINE__) ) | ||
|
||
thread_local sigjmp_buf env; | ||
template<typename Func> | ||
bool sig_check(Func f, int sig) { | ||
if (sigsetjmp(env, 1)) { | ||
// got the signal (and automatically restored handler) | ||
return true; | ||
} | ||
struct sigaction sa; | ||
// Note: we use SA_ONSTACK to have this run on the sigaltstack() stack | ||
sa.sa_flags = SA_RESETHAND | SA_ONSTACK; | ||
sigemptyset(&sa.sa_mask); | ||
sa.sa_handler = [] (int) { | ||
siglongjmp(env, 1); | ||
}; | ||
assert(sigaction(sig, &sa, NULL) == 0); | ||
f(); | ||
sa.sa_handler = SIG_DFL; | ||
assert(sigaction(sig, &sa, NULL) == 0); | ||
return false; | ||
} | ||
|
||
// endless_recursive() recurses ad infinitum, so it will finish the entire | ||
// stack and finally reach an unmapped area and cause a SIGSEGV. When it | ||
// does, a SIGSEGV handler will not be able to run on the normal thread stack | ||
// (which ran out), and only if sigaltstack() is properly supported, will the | ||
// signal handler be able to run. | ||
int endless_recursive() { | ||
return endless_recursive() + endless_recursive(); | ||
} | ||
|
||
int main(int argc, char **argv) | ||
{ | ||
// setup the per-thread sigaltstack(): | ||
stack_t sigstack; | ||
sigstack.ss_size = SIGSTKSZ; | ||
sigstack.ss_sp = malloc(sigstack.ss_size); | ||
sigstack.ss_flags = 0; | ||
|
||
// Enable the sigaltstack. Being able to catch SIGSEGV after | ||
// endless_recursive() - as we do below - will not work without it | ||
stack_t oldstack {}; | ||
expect(sigaltstack(&sigstack, &oldstack), 0); | ||
// We didn't have a sigaltstack previously, so oldstack is just disabled | ||
expect(oldstack.ss_flags, (int)SS_DISABLE); | ||
|
||
expect(sig_check(endless_recursive, SIGSEGV), true); | ||
|
||
// Check that disabling a stack works | ||
sigstack.ss_flags = SS_DISABLE; | ||
oldstack = {}; | ||
expect(sigaltstack(&sigstack, &oldstack), 0); | ||
// The previous stack was the one we had | ||
expect(oldstack.ss_sp, sigstack.ss_sp); | ||
expect(oldstack.ss_size, sigstack.ss_size); | ||
|
||
free(sigstack.ss_sp); | ||
|
||
std::cout << "SUMMARY: " << tests << " tests, " << fails << " failures\n"; | ||
return fails == 0 ? 0 : 1; | ||
} |