summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2016-06-21 12:29:18 +0000
committerDmitry Vyukov <dvyukov@google.com>2016-06-21 12:29:18 +0000
commitb0477747dfa8a9706f2c902e877e616aca51e06f (patch)
treeb1d50dd4809580b6dbbc8550adafa45a0cce0ead
parentc025f88212fc19aa62345aaf68cf6fe48733315d (diff)
[asan] add primitives that allow coroutine implementations
This patch adds the __sanitizer_start_switch_fiber and __sanitizer_finish_switch_fiber methods inspired from what can be found here https://github.com/facebook/folly/commit/2ea64dd24946cbc9f3f4ac3f6c6b98a486c56e73 . These methods are needed when the compiled software needs to implement coroutines, fibers or the like. Without a way to annotate them, when the program jumps to a stack that is not the thread stack, __asan_handle_no_return shows a warning about that, and the fake stack mechanism may free fake frames that are still in use. Author: blastrock (Philippe Daouadi) Reviewed in http://reviews.llvm.org/D20913 git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@273260 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--include/sanitizer/common_interface_defs.h20
-rw-r--r--lib/asan/asan_thread.cc105
-rw-r--r--lib/asan/asan_thread.h34
-rw-r--r--test/asan/TestCases/Linux/swapcontext_annotation.cc178
4 files changed, 324 insertions, 13 deletions
diff --git a/include/sanitizer/common_interface_defs.h b/include/sanitizer/common_interface_defs.h
index 49360f2af..7aa58e986 100644
--- a/include/sanitizer/common_interface_defs.h
+++ b/include/sanitizer/common_interface_defs.h
@@ -139,6 +139,26 @@ extern "C" {
// `top_percent` should be between 1 and 100.
// Experimental feature currently available only with asan on Linux/x86_64.
void __sanitizer_print_memory_profile(size_t top_percent);
+
+ // Fiber annotation interface.
+ // Before switching to a different stack, one must call
+ // __sanitizer_start_switch_fiber with a pointer to the bottom of the
+ // destination stack and its size. When code starts running on the new stack,
+ // it must call __sanitizer_finish_switch_fiber to finalize the switch.
+ // The start_switch function takes a void** to store the current fake stack if
+ // there is one (it is needed when detect_stack_use_after_return is enabled).
+ // When restoring a stack, this pointer must be given to the finish_switch
+ // function. In most cases, this void* can be stored on the stack just before
+ // switching. When leaving a fiber definitely, null must be passed as first
+ // argument to the start_switch function so that the fake stack is destroyed.
+ // If you do not want support for stack use-after-return detection, you can
+ // always pass null to these two functions.
+ // Note that the fake stack mechanism is disabled during fiber switch, so if a
+ // signal callback runs during the switch, it will not benefit from the stack
+ // use-after-return detection.
+ void __sanitizer_start_switch_fiber(void **fake_stack_save,
+ const void *bottom, size_t size);
+ void __sanitizer_finish_switch_fiber(void *fake_stack_save);
#ifdef __cplusplus
} // extern "C"
#endif
diff --git a/lib/asan/asan_thread.cc b/lib/asan/asan_thread.cc
index 526ef3dc5..d7e2cca65 100644
--- a/lib/asan/asan_thread.cc
+++ b/lib/asan/asan_thread.cc
@@ -120,6 +120,71 @@ void AsanThread::Destroy() {
DTLS_Destroy();
}
+void AsanThread::StartSwitchFiber(FakeStack **fake_stack_save, uptr bottom,
+ uptr size) {
+ if (atomic_load(&stack_switching_, memory_order_relaxed)) {
+ Report("ERROR: starting fiber switch while in fiber switch\n");
+ Die();
+ }
+
+ next_stack_bottom_ = bottom;
+ next_stack_top_ = bottom + size;
+ atomic_store(&stack_switching_, 1, memory_order_release);
+
+ FakeStack *current_fake_stack = fake_stack_;
+ if (fake_stack_save)
+ *fake_stack_save = fake_stack_;
+ fake_stack_ = nullptr;
+ SetTLSFakeStack(nullptr);
+ // if fake_stack_save is null, the fiber will die, delete the fakestack
+ if (!fake_stack_save && current_fake_stack)
+ current_fake_stack->Destroy(this->tid());
+}
+
+void AsanThread::FinishSwitchFiber(FakeStack *fake_stack_save) {
+ if (!atomic_load(&stack_switching_, memory_order_relaxed)) {
+ Report("ERROR: finishing a fiber switch that has not started\n");
+ Die();
+ }
+
+ if (fake_stack_save) {
+ SetTLSFakeStack(fake_stack_save);
+ fake_stack_ = fake_stack_save;
+ }
+
+ stack_bottom_ = next_stack_bottom_;
+ stack_top_ = next_stack_top_;
+ atomic_store(&stack_switching_, 0, memory_order_release);
+ next_stack_top_ = 0;
+ next_stack_bottom_ = 0;
+}
+
+inline AsanThread::StackBounds AsanThread::GetStackBounds() const {
+ if (!atomic_load(&stack_switching_, memory_order_acquire))
+ return StackBounds{stack_bottom_, stack_top_}; // NOLINT
+ char local;
+ const uptr cur_stack = (uptr)&local;
+ // Note: need to check next stack first, because FinishSwitchFiber
+ // may be in process of overwriting stack_top_/bottom_. But in such case
+ // we are already on the next stack.
+ if (cur_stack >= next_stack_bottom_ && cur_stack < next_stack_top_)
+ return StackBounds{next_stack_bottom_, next_stack_top_}; // NOLINT
+ return StackBounds{stack_bottom_, stack_top_}; // NOLINT
+}
+
+uptr AsanThread::stack_top() {
+ return GetStackBounds().top;
+}
+
+uptr AsanThread::stack_bottom() {
+ return GetStackBounds().bottom;
+}
+
+uptr AsanThread::stack_size() {
+ const auto bounds = GetStackBounds();
+ return bounds.top - bounds.bottom;
+}
+
// We want to create the FakeStack lazyly on the first use, but not eralier
// than the stack size is known and the procedure has to be async-signal safe.
FakeStack *AsanThread::AsyncSignalSafeLazyInitFakeStack() {
@@ -150,6 +215,8 @@ FakeStack *AsanThread::AsyncSignalSafeLazyInitFakeStack() {
}
void AsanThread::Init() {
+ next_stack_top_ = next_stack_bottom_ = 0;
+ atomic_store(&stack_switching_, false, memory_order_release);
fake_stack_ = nullptr; // Will be initialized lazily if needed.
CHECK_EQ(this->stack_size(), 0U);
SetThreadStackAndTls();
@@ -195,9 +262,10 @@ thread_return_t AsanThread::ThreadStart(
void AsanThread::SetThreadStackAndTls() {
uptr tls_size = 0;
- GetThreadStackAndTls(tid() == 0, &stack_bottom_, &stack_size_, &tls_begin_,
- &tls_size);
- stack_top_ = stack_bottom_ + stack_size_;
+ uptr stack_size = 0;
+ GetThreadStackAndTls(tid() == 0, const_cast<uptr *>(&stack_bottom_),
+ const_cast<uptr *>(&stack_size), &tls_begin_, &tls_size);
+ stack_top_ = stack_bottom_ + stack_size;
tls_end_ = tls_begin_ + tls_size;
dtls_ = DTLS_Get();
@@ -250,6 +318,11 @@ bool AsanThread::GetStackFrameAccessByAddr(uptr addr,
return true;
}
+bool AsanThread::AddrIsInStack(uptr addr) {
+ const auto bounds = GetStackBounds();
+ return addr >= bounds.bottom && addr < bounds.top;
+}
+
static bool ThreadStackContainsAddress(ThreadContextBase *tctx_base,
void *addr) {
AsanThreadContext *tctx = static_cast<AsanThreadContext*>(tctx_base);
@@ -357,3 +430,29 @@ void EnsureMainThreadIDIsCorrect() {
__asan::EnsureMainThreadIDIsCorrect();
}
} // namespace __lsan
+
+// ---------------------- Interface ---------------- {{{1
+using namespace __asan; // NOLINT
+
+extern "C" {
+SANITIZER_INTERFACE_ATTRIBUTE
+void __sanitizer_start_switch_fiber(void **fakestacksave, const void *bottom,
+ uptr size) {
+ AsanThread *t = GetCurrentThread();
+ if (!t) {
+ VReport(1, "__asan_start_switch_fiber called from unknown thread\n");
+ return;
+ }
+ t->StartSwitchFiber((FakeStack**)fakestacksave, (uptr)bottom, size);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __sanitizer_finish_switch_fiber(void* fakestack) {
+ AsanThread *t = GetCurrentThread();
+ if (!t) {
+ VReport(1, "__asan_finish_switch_fiber called from unknown thread\n");
+ return;
+ }
+ t->FinishSwitchFiber((FakeStack*)fakestack);
+}
+}
diff --git a/lib/asan/asan_thread.h b/lib/asan/asan_thread.h
index b05d720d6..92a92a2e8 100644
--- a/lib/asan/asan_thread.h
+++ b/lib/asan/asan_thread.h
@@ -66,9 +66,9 @@ class AsanThread {
thread_return_t ThreadStart(uptr os_id,
atomic_uintptr_t *signal_thread_is_registered);
- uptr stack_top() { return stack_top_; }
- uptr stack_bottom() { return stack_bottom_; }
- uptr stack_size() { return stack_size_; }
+ uptr stack_top();
+ uptr stack_bottom();
+ uptr stack_size();
uptr tls_begin() { return tls_begin_; }
uptr tls_end() { return tls_end_; }
DTLS *dtls() { return dtls_; }
@@ -83,9 +83,7 @@ class AsanThread {
};
bool GetStackFrameAccessByAddr(uptr addr, StackFrameAccess *access);
- bool AddrIsInStack(uptr addr) {
- return addr >= stack_bottom_ && addr < stack_top_;
- }
+ bool AddrIsInStack(uptr addr);
void DeleteFakeStack(int tid) {
if (!fake_stack_) return;
@@ -95,13 +93,19 @@ class AsanThread {
t->Destroy(tid);
}
+ void StartSwitchFiber(FakeStack **fake_stack_save, uptr bottom, uptr size);
+ void FinishSwitchFiber(FakeStack *fake_stack_save);
+
bool has_fake_stack() {
- return (reinterpret_cast<uptr>(fake_stack_) > 1);
+ return !atomic_load(&stack_switching_, memory_order_relaxed) &&
+ (reinterpret_cast<uptr>(fake_stack_) > 1);
}
FakeStack *fake_stack() {
if (!__asan_option_detect_stack_use_after_return)
return nullptr;
+ if (atomic_load(&stack_switching_, memory_order_relaxed))
+ return nullptr;
if (!has_fake_stack())
return AsyncSignalSafeLazyInitFakeStack();
return fake_stack_;
@@ -127,14 +131,24 @@ class AsanThread {
void ClearShadowForThreadStackAndTLS();
FakeStack *AsyncSignalSafeLazyInitFakeStack();
+ struct StackBounds {
+ uptr bottom;
+ uptr top;
+ };
+ StackBounds GetStackBounds() const;
+
AsanThreadContext *context_;
thread_callback_t start_routine_;
void *arg_;
+
uptr stack_top_;
uptr stack_bottom_;
- // stack_size_ == stack_top_ - stack_bottom_;
- // It needs to be set in a async-signal-safe manner.
- uptr stack_size_;
+ // these variables are used when the thread is about to switch stack
+ uptr next_stack_top_;
+ uptr next_stack_bottom_;
+ // true if switching is in progress
+ atomic_uint8_t stack_switching_;
+
uptr tls_begin_;
uptr tls_end_;
DTLS *dtls_;
diff --git a/test/asan/TestCases/Linux/swapcontext_annotation.cc b/test/asan/TestCases/Linux/swapcontext_annotation.cc
new file mode 100644
index 000000000..90aabaee2
--- /dev/null
+++ b/test/asan/TestCases/Linux/swapcontext_annotation.cc
@@ -0,0 +1,178 @@
+// Check that ASan plays well with annotated makecontext/swapcontext.
+
+// RUN: %clangxx_asan -lpthread -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_asan -lpthread -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_asan -lpthread -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_asan -lpthread -O3 %s -o %t && %run %t 2>&1 | FileCheck %s
+//
+// This test is too subtle to try on non-x86 arch for now.
+// REQUIRES: x86_64-supported-target,i386-supported-target
+
+#include <pthread.h>
+#include <setjmp.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <ucontext.h>
+#include <unistd.h>
+
+#include <sanitizer/common_interface_defs.h>
+
+ucontext_t orig_context;
+ucontext_t child_context;
+ucontext_t next_child_context;
+
+char *next_child_stack;
+
+const int kStackSize = 1 << 20;
+
+void *main_thread_stack;
+size_t main_thread_stacksize;
+
+__attribute__((noinline, noreturn)) void LongJump(jmp_buf env) {
+ longjmp(env, 1);
+ _exit(1);
+}
+
+// Simulate __asan_handle_no_return().
+__attribute__((noinline)) void CallNoReturn() {
+ jmp_buf env;
+ if (setjmp(env) != 0) return;
+
+ LongJump(env);
+ _exit(1);
+}
+
+void NextChild() {
+ CallNoReturn();
+ __sanitizer_finish_switch_fiber();
+
+ char x[32] = {0}; // Stack gets poisoned.
+ printf("NextChild: %p\n", x);
+
+ CallNoReturn();
+
+ __sanitizer_start_switch_fiber(main_thread_stack, main_thread_stacksize);
+ CallNoReturn();
+ if (swapcontext(&next_child_context, &orig_context) < 0) {
+ perror("swapcontext");
+ _exit(1);
+ }
+}
+
+void Child(int mode) {
+ CallNoReturn();
+ __sanitizer_finish_switch_fiber();
+ char x[32] = {0}; // Stack gets poisoned.
+ printf("Child: %p\n", x);
+ CallNoReturn();
+ // (a) Do nothing, just return to parent function.
+ // (b) Jump into the original function. Stack remains poisoned unless we do
+ // something.
+ // (c) Jump to another function which will then jump back to the main function
+ if (mode == 0) {
+ __sanitizer_start_switch_fiber(main_thread_stack, main_thread_stacksize);
+ CallNoReturn();
+ } else if (mode == 1) {
+ __sanitizer_start_switch_fiber(main_thread_stack, main_thread_stacksize);
+ CallNoReturn();
+ if (swapcontext(&child_context, &orig_context) < 0) {
+ perror("swapcontext");
+ _exit(1);
+ }
+ } else if (mode == 2) {
+ getcontext(&next_child_context);
+ next_child_context.uc_stack.ss_sp = next_child_stack;
+ next_child_context.uc_stack.ss_size = kStackSize / 2;
+ makecontext(&next_child_context, (void (*)())NextChild, 0);
+ __sanitizer_start_switch_fiber(next_child_context.uc_stack.ss_sp,
+ next_child_context.uc_stack.ss_size);
+ CallNoReturn();
+ if (swapcontext(&child_context, &next_child_context) < 0) {
+ perror("swapcontext");
+ _exit(1);
+ }
+ }
+}
+
+int Run(int arg, int mode, char *child_stack) {
+ printf("Child stack: %p\n", child_stack);
+ // Setup child context.
+ getcontext(&child_context);
+ child_context.uc_stack.ss_sp = child_stack;
+ child_context.uc_stack.ss_size = kStackSize / 2;
+ if (mode == 0) {
+ child_context.uc_link = &orig_context;
+ }
+ makecontext(&child_context, (void (*)())Child, 1, mode);
+ CallNoReturn();
+ __sanitizer_start_switch_fiber(child_context.uc_stack.ss_sp,
+ child_context.uc_stack.ss_size);
+ CallNoReturn();
+ if (swapcontext(&orig_context, &child_context) < 0) {
+ perror("swapcontext");
+ _exit(1);
+ }
+ CallNoReturn();
+ __sanitizer_finish_switch_fiber();
+ CallNoReturn();
+
+ // Touch childs's stack to make sure it's unpoisoned.
+ for (int i = 0; i < kStackSize; i++) {
+ child_stack[i] = i;
+ }
+ return child_stack[arg];
+}
+
+void handler(int sig) { CallNoReturn(); }
+
+void InitStackBounds() {
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_getattr_np(pthread_self(), &attr);
+ pthread_attr_getstack(&attr, &main_thread_stack, &main_thread_stacksize);
+ pthread_attr_destroy(&attr);
+}
+
+int main(int argc, char **argv) {
+ InitStackBounds();
+
+ // set up a signal that will spam and trigger __asan_handle_no_return at
+ // tricky moments
+ struct sigaction act = {};
+ act.sa_handler = &handler;
+ if (sigaction(SIGPROF, &act, 0)) {
+ perror("sigaction");
+ _exit(1);
+ }
+
+ itimerval t;
+ t.it_interval.tv_sec = 0;
+ t.it_interval.tv_usec = 10;
+ t.it_value = t.it_interval;
+ if (setitimer(ITIMER_PROF, &t, 0)) {
+ perror("setitimer");
+ _exit(1);
+ }
+
+ char *heap = new char[kStackSize + 1];
+ next_child_stack = new char[kStackSize + 1];
+ char stack[kStackSize + 1];
+ // CHECK: WARNING: ASan doesn't fully support makecontext/swapcontext
+ int ret = 0;
+ // CHECK-NOT: ASan is ignoring requested __asan_handle_no_return
+ for (unsigned int i = 0; i < 30; ++i) {
+ ret += Run(argc - 1, 0, stack);
+ ret += Run(argc - 1, 1, stack);
+ ret += Run(argc - 1, 2, stack);
+ ret += Run(argc - 1, 0, heap);
+ ret += Run(argc - 1, 1, heap);
+ ret += Run(argc - 1, 2, heap);
+ }
+ // CHECK: Test passed
+ printf("Test passed\n");
+
+ delete[] heap;
+ delete[] next_child_stack;
+
+ return ret;
+}