-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Backtrace on fatal signals. #855
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
817eaec
2c81ad2
942c63a
967238e
f7e5ab5
b58da42
0ab65ec
d27fda1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| #include "exe/signal_action.h" | ||
|
|
||
| #include <signal.h> | ||
| #include <sys/mman.h> | ||
|
|
||
| #include "common/common/assert.h" | ||
|
|
||
| constexpr int SignalAction::FATAL_SIGS[]; | ||
|
|
||
| void SignalAction::sigHandler(int sig, siginfo_t* info, void* context) { | ||
| void* error_pc = 0; | ||
|
|
||
| const ucontext_t* ucontext = reinterpret_cast<const ucontext_t*>(context); | ||
| if (ucontext != nullptr) { | ||
| #ifdef REG_RIP | ||
| // x86_64 | ||
| error_pc = reinterpret_cast<void*>(ucontext->uc_mcontext.gregs[REG_RIP]); | ||
| #else | ||
| #warning "Please enable and test PC retrieval code for your arch in signal_action.cc" | ||
| // x86 Classic: reinterpret_cast<void*>(ucontext->uc_mcontext.gregs[REG_EIP]); | ||
| // ARM: reinterpret_cast<void*>(ucontext->uc_mcontext.arm_pc); | ||
| // PPC: reinterpret_cast<void*>(ucontext->uc_mcontext.regs->nip); | ||
| #endif | ||
| } | ||
|
|
||
| BackwardsTrace tracer; | ||
| tracer.logFault(strsignal(sig), info->si_addr); | ||
| if (error_pc != 0) { | ||
| tracer.captureFrom(error_pc); | ||
| } else { | ||
| tracer.capture(); | ||
| } | ||
| tracer.logTrace(); | ||
|
|
||
| signal(sig, SIG_DFL); | ||
| raise(sig); | ||
| } | ||
|
|
||
| void SignalAction::installSigHandlers() { | ||
| stack_t stack; | ||
| stack.ss_sp = altstack_ + guard_size_; // Guard page at one end ... | ||
| stack.ss_size = altstack_size_; // ... guard page at the other | ||
| stack.ss_flags = 0; | ||
|
|
||
| RELEASE_ASSERT(sigaltstack(&stack, nullptr) == 0); | ||
|
|
||
| for (const auto& sig : FATAL_SIGS) { | ||
| struct sigaction saction; | ||
| std::memset(&saction, 0, sizeof(saction)); | ||
| sigemptyset(&saction.sa_mask); | ||
| saction.sa_flags = (SA_SIGINFO | SA_ONSTACK | SA_RESETHAND | SA_NODEFER); | ||
| saction.sa_sigaction = sigHandler; | ||
| RELEASE_ASSERT(sigaction(sig, &saction, nullptr) == 0); | ||
| } | ||
| } | ||
|
|
||
| void SignalAction::removeSigHandlers() { | ||
| for (const auto& sig : FATAL_SIGS) { | ||
| signal(sig, SIG_DFL); | ||
| } | ||
| } | ||
|
|
||
| void SignalAction::mapAndProtectStackMemory() { | ||
| // Per docs MAP_STACK doesn't actually do anything today but provides a | ||
| // library hint that might be used in the future. | ||
| altstack_ = static_cast<char*>(mmap(nullptr, mapSizeWithGuards(), PROT_READ | PROT_WRITE, | ||
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0)); | ||
| RELEASE_ASSERT(altstack_); | ||
| RELEASE_ASSERT(mprotect(altstack_, guard_size_, PROT_NONE) == 0); | ||
| RELEASE_ASSERT(mprotect(altstack_ + guard_size_ + altstack_size_, guard_size_, PROT_NONE) == 0); | ||
| } | ||
|
|
||
| void SignalAction::unmapStackMemory() { munmap(altstack_, mapSizeWithGuards()); } | ||
|
|
||
| void SignalAction::doGoodAccessForTest() { | ||
| volatile char* altaltstack = altstack_; | ||
| for (size_t i = 0; i < altstack_size_; ++i) { | ||
| *(altaltstack + guard_size_ + i) = 42; | ||
| } | ||
| for (size_t i = 0; i < altstack_size_; ++i) { | ||
| ASSERT(*(altaltstack + guard_size_ + i) == 42); | ||
| } | ||
| } | ||
|
|
||
| void SignalAction::tryEvilAccessForTest(bool end) { | ||
| volatile char* altaltstack = altstack_; | ||
| if (end) { | ||
| // One byte past the valid region | ||
| // http://oeis.org/A001969 | ||
| *(altaltstack + guard_size_ + altstack_size_) = 43; | ||
| } else { | ||
| // One byte before the valid region | ||
| *(altaltstack + guard_size_ - 1) = 43; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| #pragma once | ||
|
|
||
| #include <signal.h> | ||
| #include <unistd.h> | ||
|
|
||
| #include "common/common/non_copyable.h" | ||
|
|
||
| #include "server/backtrace.h" | ||
|
|
||
| /** | ||
| * This class installs signal handlers for fatal signal types. | ||
| * | ||
| * These signals are handled: | ||
| * SIGABRT | ||
| * SIGBUS | ||
| * SIGFPE | ||
| * SIGILL | ||
| * SIGSEGV | ||
| * | ||
| * Upon intercepting the signal the following actions are taken: | ||
| * | ||
| * A Backtrace is printed from the address the signal was encountered at, if | ||
| * it is possible to retrieve. | ||
| * | ||
| * The signal handler is reset to the default handler (which is expected to | ||
| * terminate the process). | ||
| * | ||
| * The signal is raised again (which ultimately kills the process) | ||
| * | ||
| * The signal handler must run on an alternative stack so that we can do the | ||
| * stack unwind on the original stack. Memory is allocated for this purpose when | ||
| * this object is constructed. When this object goes out of scope the memory | ||
| * used for the alternate signal stack is destroyed and the default signal handler | ||
| * is restored. | ||
| * | ||
| * NOTE: Existing non-default signal handlers are overridden and will not be | ||
| * restored. If this behavior is ever required it can be implemented. | ||
| * | ||
| * It is recommended that this object be instantiated at the highest possible | ||
| * scope, eg, in main(). This enables fatal signal handling for almost all code | ||
| * executed. | ||
| */ | ||
| class SignalAction : NonCopyable { | ||
| public: | ||
| SignalAction() | ||
| : guard_size_(sysconf(_SC_PAGE_SIZE)), altstack_size_(guard_size_ * 4), altstack_(nullptr) { | ||
| mapAndProtectStackMemory(); | ||
| installSigHandlers(); | ||
| } | ||
| ~SignalAction() { | ||
| removeSigHandlers(); | ||
| unmapStackMemory(); | ||
| } | ||
| /** | ||
| * Helpers for testing guarded stack memory | ||
| */ | ||
| void doGoodAccessForTest(); | ||
| void tryEvilAccessForTest(bool end); | ||
|
|
||
| private: | ||
| /** | ||
| * Allocate this many bytes on each side of the area used for alt stack. | ||
| * | ||
| * Set to system page size. | ||
| * | ||
| * The memory will be protected from read and write. | ||
| */ | ||
| const size_t guard_size_; | ||
| /** | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor nit: we don't need Doxygen documentation for internal implementation variables, you can use regular C++ comments.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason NOT to use Doxygen formatted comments here? Doxygen can still produce useful documentation for private member vars depending on how it is configured to run. I've found this information to be useful in past projects.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None other than inconsistency - if we don't have this documented as a convention across the code base in our style guide, then any Doxygen documentation on internal implementation details is a bit special snowflake. For example, unless we do have widespread agreement to use Doxygen for all documents, I plan on continuing to use // comments for internal comments. |
||
| * Use this many bytes for the alternate signal handling stack. | ||
| * | ||
| * Initialized as a multiple of page size (although sigaltstack will | ||
| * do alignment as needed). | ||
| * | ||
| * Additionally, two guard pages will be allocated to bookend the usable area. | ||
| */ | ||
| const size_t altstack_size_; | ||
| /** | ||
| * This constant array defines the signals we will insert handlers for. | ||
| * | ||
| * Essentially this is the list of signals that would cause a core dump. | ||
| */ | ||
| static constexpr int FATAL_SIGS[] = {SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV}; | ||
| /** | ||
| * Return the memory size we actually map including two guard pages. | ||
| */ | ||
| size_t mapSizeWithGuards() const { return altstack_size_ + guard_size_ * 2; } | ||
| /** | ||
| * The actual signal handler function with prototype matching signal.h | ||
| */ | ||
| static void sigHandler(int sig, siginfo_t* info, void* context); | ||
| /** | ||
| * Install all signal handlers and setup signal handling stack. | ||
| */ | ||
| void installSigHandlers(); | ||
| /** | ||
| * Remove all signal handlers. | ||
| * | ||
| * Must be executed before the alt stack memory goes away. | ||
| * | ||
| * Signal handlers will be reset to the default, NOT back to any signal | ||
| * handler existing before InstallSigHandlers(). | ||
| */ | ||
| void removeSigHandlers(); | ||
| /** | ||
| * Use mmap to map anonymous memory for the alternative stack. | ||
| * | ||
| * GUARD_SIZE on either end of the memory will be marked PROT_NONE, protected | ||
| * from all access. | ||
| */ | ||
| void mapAndProtectStackMemory(); | ||
| /** | ||
| * Unmap alternative stack memory. | ||
| */ | ||
| void unmapStackMemory(); | ||
| char* altstack_; | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to set this in the
tools/bazel.rcfor the ASAN runs. I'm not sure where ASAN installs its signal handlers (based on your experiences with the tests, it seems after this), but this might be safer in general to avoid conflict.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be done now