From 1e68fd41b434b1df426ce7e03fcb7199b25161fa Mon Sep 17 00:00:00 2001 From: winlin Date: Thu, 22 Aug 2024 15:19:52 +0800 Subject: [PATCH] ST: Support asan fiber switch for stack tracing. --- trunk/3rdparty/st-srs/Makefile | 6 +++ trunk/3rdparty/st-srs/README.md | 6 +++ trunk/3rdparty/st-srs/common.c | 9 +++++ trunk/3rdparty/st-srs/common.h | 61 ++++++++++++++++++++++++++++++ trunk/3rdparty/st-srs/public.h | 2 + trunk/3rdparty/st-srs/sched.c | 16 +++++--- trunk/research/st/.gitignore | 1 + trunk/research/st/asan-switch.cpp | 18 ++++++++- trunk/research/st/hello-thread.cpp | 21 ++++++++++ trunk/research/st/hello.c | 9 +++++ 10 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 trunk/research/st/hello-thread.cpp create mode 100644 trunk/research/st/hello.c diff --git a/trunk/3rdparty/st-srs/Makefile b/trunk/3rdparty/st-srs/Makefile index c5590380d90..fa9a98687b4 100644 --- a/trunk/3rdparty/st-srs/Makefile +++ b/trunk/3rdparty/st-srs/Makefile @@ -195,6 +195,12 @@ endif # or cache the stack and reuse it: # make EXTRA_CFLAGS=-DMD_CACHE_STACK # +# or enable support for valgrind: +# make EXTRA_CFLAGS="-DMD_VALGRIND" +# +# or enable support for asan: +# make EXTRA_CFLAGS="-DMD_ASAN" +# # or enable the coverage for utest: # make UTEST_FLAGS="-fprofile-arcs -ftest-coverage" # diff --git a/trunk/3rdparty/st-srs/README.md b/trunk/3rdparty/st-srs/README.md index e1959a73495..f033d8eaeeb 100644 --- a/trunk/3rdparty/st-srs/README.md +++ b/trunk/3rdparty/st-srs/README.md @@ -51,6 +51,12 @@ Linux with valgrind and epoll: make linux-debug EXTRA_CFLAGS="-DMD_HAVE_EPOLL -DMD_VALGRIND" ``` +Linux with ASAN(Google Address Sanitizer): + +```bash +make linux-debug EXTRA_CFLAGS="-DMD_ASAN" +``` + ## Mac: Usage Get code: diff --git a/trunk/3rdparty/st-srs/common.c b/trunk/3rdparty/st-srs/common.c index 1b71b17552c..e241ec5f374 100644 --- a/trunk/3rdparty/st-srs/common.c +++ b/trunk/3rdparty/st-srs/common.c @@ -3,3 +3,12 @@ #include "common.h" +void *_st_primordial_stack_bottom = NULL; +size_t _st_primordial_stack_size = 0; + +void st_set_primordial_stack(void *top, void *bottom) +{ + _st_primordial_stack_bottom = bottom; + _st_primordial_stack_size = (char *)top - (char *)bottom; +} + diff --git a/trunk/3rdparty/st-srs/common.h b/trunk/3rdparty/st-srs/common.h index c82a01e8b69..ccd6cfff84e 100644 --- a/trunk/3rdparty/st-srs/common.h +++ b/trunk/3rdparty/st-srs/common.h @@ -162,6 +162,10 @@ struct _st_thread { _st_clist_t tlink; /* For putting on thread queue */ #endif +#ifdef MD_ASAN + void *fake_stack; /* Fake stack for ASAN */ +#endif + st_utime_t due; /* Wakeup time when thread is sleeping */ _st_thread_t *left; /* For putting in timeout heap */ _st_thread_t *right; /* -- see docs/timeout_heap.txt for details */ @@ -365,6 +369,40 @@ _st_thread_t *st_thread_create(void *(*start)(void *arg), void *arg, int joinabl #define ST_SWITCH_IN_CB(_thread) #endif +#ifdef MD_ASAN +/* + * 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. + * + * See https://github.com/google/sanitizers/issues/189#issuecomment-1346243598 + */ +extern void __sanitizer_start_switch_fiber(void **fake_stack_save, + const void *bottom, size_t size); + +extern void __sanitizer_finish_switch_fiber(void *fake_stack_save, + const void **bottom_old, + size_t *size_old); + +/* The stack for primoridal thread. */ +extern void *_st_primordial_stack_bottom; +extern size_t _st_primordial_stack_size; +#endif + /* * Switch away from the current thread context by saving its state and * calling the thread scheduler @@ -377,6 +415,11 @@ static inline void _st_switch_context(_st_thread_t *thread) _st_vp_schedule(thread); } +#ifdef MD_ASAN + /* Switch from other thread to this running thread. */ + __sanitizer_finish_switch_fiber(thread->fake_stack, NULL, NULL); +#endif + ST_DEBUG_ITERATE_THREADS(); ST_SWITCH_IN_CB(thread); } @@ -387,6 +430,24 @@ static inline void _st_switch_context(_st_thread_t *thread) */ static inline void _st_restore_context(_st_thread_t *from, _st_thread_t *thread) { +#ifdef MD_ASAN + /* For primordial thread, the stack is NULL, so asan can not capture it. */ + const void *stk_bottom = thread->stack ? thread->stack->stk_bottom : NULL; + size_t stk_size = thread->stack ? thread->stack->stk_size : 0; + + /* For primordial thread, user should setup the stack information. */ + if (!stk_bottom && (thread->flags & _ST_FL_PRIMORDIAL)) { + stk_bottom = _st_primordial_stack_bottom; + stk_size = _st_primordial_stack_size; + } + + /* + * Save the current stack to fake_stack of from, tell asan the target stack + * we are targeting to switch to. + */ + __sanitizer_start_switch_fiber(&from->fake_stack, stk_bottom, stk_size); +#endif + _st_this_thread = thread; _st_md_cxt_restore(thread->context, 1); } diff --git a/trunk/3rdparty/st-srs/public.h b/trunk/3rdparty/st-srs/public.h index 97da1464299..6555c02c0e0 100644 --- a/trunk/3rdparty/st-srs/public.h +++ b/trunk/3rdparty/st-srs/public.h @@ -159,6 +159,8 @@ extern st_netfd_t st_open(const char *path, int oflags, mode_t mode); extern void st_destroy(void); extern int st_thread_setspecific2(st_thread_t thread, int key, void *value); +extern void st_set_primordial_stack(void *top, void *bottom); + #ifdef DEBUG extern void _st_show_thread_stack(st_thread_t thread, const char *messg); extern void _st_iterate_threads(void); diff --git a/trunk/3rdparty/st-srs/sched.c b/trunk/3rdparty/st-srs/sched.c index a543a060196..1d2e35f93cd 100644 --- a/trunk/3rdparty/st-srs/sched.c +++ b/trunk/3rdparty/st-srs/sched.c @@ -368,6 +368,11 @@ int st_thread_join(_st_thread_t *thread, void **retvalp) void _st_thread_main(void) { _st_thread_t *thread = _st_this_thread; + +#ifdef MD_ASAN + /* Switch from other thread to this new created thread. */ + __sanitizer_finish_switch_fiber(thread->fake_stack, NULL, NULL); +#endif /* * Cap the stack by zeroing out the saved return address register @@ -572,7 +577,7 @@ void _st_vp_check_clock(void) /* Make thread runnable */ ST_ASSERT(!(thread->flags & _ST_FL_IDLE_THREAD)); thread->state = _ST_ST_RUNNABLE; - // Insert at the head of RunQ, to execute timer first. + /* Insert at the head of RunQ, to execute timer first. */ st_clist_insert_after(&thread->links, &_st_this_vp.run_q); } } @@ -589,7 +594,7 @@ void st_thread_yield() /* Check sleep queue for expired threads */ _st_vp_check_clock(); - // If not thread in RunQ to yield to, ignore and continue to run. + /* If not thread in RunQ to yield to, ignore and continue to run. */ if (_st_this_vp.run_q.next == &_st_this_vp.run_q) { return; } @@ -598,11 +603,11 @@ void st_thread_yield() ++_st_stat_thread_yield2; #endif - // Append thread to the tail of RunQ, we will back after all threads executed. + /* Append thread to the tail of RunQ, we will back after all threads executed. */ me->state = _ST_ST_RUNNABLE; st_clist_insert_before(&me->links, &_st_this_vp.run_q); - // Yield to other threads in the RunQ. + /* Yield to other threads in the RunQ. */ _st_switch_context(me); } @@ -664,8 +669,9 @@ _st_thread_t *st_thread_create(void *(*start)(void *arg), void *arg, int joinabl thread->arg = arg; /* Note that we must directly call rather than call any functions. */ - if (_st_md_cxt_save(thread->context)) + if (_st_md_cxt_save(thread->context)) { _st_thread_main(); + } MD_GET_SP(thread) = (long)(stack->sp); /* If thread is joinable, allocate a termination condition variable */ diff --git a/trunk/research/st/.gitignore b/trunk/research/st/.gitignore index 952febfff2c..d24fbd32a79 100644 --- a/trunk/research/st/.gitignore +++ b/trunk/research/st/.gitignore @@ -11,3 +11,4 @@ exceptions hello pthreads asan-switch +hello-thread diff --git a/trunk/research/st/asan-switch.cpp b/trunk/research/st/asan-switch.cpp index 8a7e094fa61..f37b8ac60f2 100644 --- a/trunk/research/st/asan-switch.cpp +++ b/trunk/research/st/asan-switch.cpp @@ -3,12 +3,17 @@ g++ asan-switch.cpp ../../objs/st/libst.a -fsanitize=address -fno-omit-frame-poi */ #include #include +#include #include "../../objs/st/st.h" +extern "C" { +extern void st_set_primordial_stack(void* top, void* bottom); +} + void* foo(void *args) { for (int i = 0; ; i++) { st_sleep(1); - if (i && (i % 3) == 0) { + if (i && (i % 2) == 0) { char *p = new char[3]; p[3] = 'H'; } @@ -18,9 +23,18 @@ void* foo(void *args) { } int main(int argc, char **argv) { + register void* stack_top asm ("sp"); + struct rlimit limit; + if (getrlimit (RLIMIT_STACK, &limit) == 0) { + void* stack_bottom = (char*)stack_top - limit.rlim_cur; + st_set_primordial_stack(stack_top, stack_bottom); + } + st_init(); if (argc > 1) { - foo(NULL); // Directly call foo() to trigger ASAN. + // Directly call foo() to trigger ASAN, call the function in the primordial thread, + // note that asan can not capther the stack of primordial thread. + foo(NULL); } else { st_thread_create(foo, NULL, 0, 0); st_thread_exit(NULL); diff --git a/trunk/research/st/hello-thread.cpp b/trunk/research/st/hello-thread.cpp new file mode 100644 index 00000000000..3128ef80a0b --- /dev/null +++ b/trunk/research/st/hello-thread.cpp @@ -0,0 +1,21 @@ +/* +g++ hello-thread.cpp ../../objs/st/libst.a -g -O0 -o hello-thread && ./hello-thread +*/ +#include +#include "../../objs/st/st.h" + +void* foo(void *args) { + for (int i = 0; ; i++) { + st_sleep(1); + printf("#%d: main: working\n", i); + } + + return NULL; +} + +int main() { + st_init(); + st_thread_create(foo, NULL, 0, 0); + st_thread_exit(NULL); + return 0; +} diff --git a/trunk/research/st/hello.c b/trunk/research/st/hello.c new file mode 100644 index 00000000000..8000cb13159 --- /dev/null +++ b/trunk/research/st/hello.c @@ -0,0 +1,9 @@ +#include +#include +int main (void) +{ + struct rlimit limit; + + getrlimit (RLIMIT_STACK, &limit); + printf ("\nStack Limit = %ld and %ld max\n", limit.rlim_cur, limit.rlim_max); +}