diff --git a/cont.c b/cont.c index ee7b856bb1..713f8cb952 100644 --- a/cont.c +++ b/cont.c @@ -274,7 +274,6 @@ static ID fiber_initialize_keywords[2] = {0}; #define ERRNOMSG strerror(errno) // Locates the stack vacancy details for the given stack. -// Requires that fiber_pool_vacancy fits within one page. inline static struct fiber_pool_vacancy * fiber_pool_vacancy_pointer(void * base, size_t size) { @@ -285,6 +284,24 @@ fiber_pool_vacancy_pointer(void * base, size_t size) ); } +#if defined(COROUTINE_SANITIZE_ADDRESS) +// Compute the base pointer for a vacant stack, for the area which can be poisoned. +inline static void * +fiber_pool_stack_poison_base(struct fiber_pool_stack * stack) +{ + STACK_GROW_DIR_DETECTION; + + return (char*)stack->base + STACK_DIR_UPPER(RB_PAGE_SIZE, 0); +} + +// Compute the size of the vacant stack, for the area that can be poisoned. +inline static size_t +fiber_pool_stack_poison_size(struct fiber_pool_stack * stack) +{ + return stack->size - RB_PAGE_SIZE; +} +#endif + // Reset the current stack pointer and available size of the given stack. inline static void fiber_pool_stack_reset(struct fiber_pool_stack * stack) @@ -634,6 +651,10 @@ fiber_pool_stack_acquire(struct fiber_pool * fiber_pool) VM_ASSERT(vacancy); VM_ASSERT(vacancy->stack.base); +#if defined(COROUTINE_SANITIZE_ADDRESS) + __asan_unpoison_memory_region(fiber_pool_stack_poison_base(&vacancy->stack), fiber_pool_stack_poison_size(&vacancy->stack)); +#endif + // Take the top item from the free list: fiber_pool->used += 1; @@ -679,6 +700,10 @@ fiber_pool_stack_free(struct fiber_pool_stack * stack) // Not available in all versions of Windows. //DiscardVirtualMemory(base, size); #endif + +#if defined(COROUTINE_SANITIZE_ADDRESS) + __asan_poison_memory_region(fiber_pool_stack_poison_base(stack), fiber_pool_stack_poison_size(stack)); +#endif } // Release and return a stack to the vacancy list. @@ -698,7 +723,7 @@ fiber_pool_stack_release(struct fiber_pool_stack * stack) fiber_pool_vacancy_reset(vacancy); // Push the vacancy into the vancancies list: - pool->vacancies = fiber_pool_vacancy_push(vacancy, stack->pool->vacancies); + pool->vacancies = fiber_pool_vacancy_push(vacancy, pool->vacancies); pool->used -= 1; #ifdef FIBER_POOL_ALLOCATION_FREE @@ -751,6 +776,11 @@ static COROUTINE fiber_entry(struct coroutine_context * from, struct coroutine_context * to) { rb_fiber_t *fiber = to->argument; + +#if defined(COROUTINE_SANITIZE_ADDRESS) + __sanitizer_finish_switch_fiber(to->fake_stack, NULL, NULL); +#endif + rb_thread_t *thread = fiber->cont.saved_ec.thread_ptr; #ifdef COROUTINE_PTHREAD_CONTEXT @@ -1379,9 +1409,17 @@ fiber_setcontext(rb_fiber_t *new_fiber, rb_fiber_t *old_fiber) // if (DEBUG) fprintf(stderr, "fiber_setcontext: %p[%p] -> %p[%p]\n", (void*)old_fiber, old_fiber->stack.base, (void*)new_fiber, new_fiber->stack.base); +#if defined(COROUTINE_SANITIZE_ADDRESS) + __sanitizer_start_switch_fiber(FIBER_TERMINATED_P(old_fiber) ? NULL : &old_fiber->context.fake_stack, new_fiber->context.stack_base, new_fiber->context.stack_size); +#endif + /* swap machine context */ struct coroutine_context * from = coroutine_transfer(&old_fiber->context, &new_fiber->context); +#if defined(COROUTINE_SANITIZE_ADDRESS) + __sanitizer_finish_switch_fiber(old_fiber->context.fake_stack, NULL, NULL); +#endif + if (from == NULL) { rb_syserr_fail(errno, "coroutine_transfer"); } diff --git a/coroutine/amd64/Context.h b/coroutine/amd64/Context.h index f626a47225..44daa4e01a 100644 --- a/coroutine/amd64/Context.h +++ b/coroutine/amd64/Context.h @@ -19,10 +19,29 @@ enum {COROUTINE_REGISTERS = 6}; +#if defined(__SANITIZE_ADDRESS__) + #define COROUTINE_SANITIZE_ADDRESS +#elif defined(__has_feature) + #if __has_feature(address_sanitizer) + #define COROUTINE_SANITIZE_ADDRESS + #endif +#endif + +#if defined(COROUTINE_SANITIZE_ADDRESS) +#include +#include +#endif + struct coroutine_context { void **stack_pointer; void *argument; + +#if defined(COROUTINE_SANITIZE_ADDRESS) + void *fake_stack; + void *stack_base; + size_t stack_size; +#endif }; typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self); @@ -39,6 +58,12 @@ static inline void coroutine_initialize( ) { assert(start && stack && size >= 1024); +#if defined(COROUTINE_SANITIZE_ADDRESS) + context->fake_stack = NULL; + context->stack_base = stack; + context->stack_size = size; +#endif + // Stack grows down. Force 16-byte alignment. char * top = (char*)stack + size; context->stack_pointer = (void**)((uintptr_t)top & ~0xF); diff --git a/coroutine/arm64/Context.h b/coroutine/arm64/Context.h index dbc6ac94fb..1472621f48 100644 --- a/coroutine/arm64/Context.h +++ b/coroutine/arm64/Context.h @@ -19,10 +19,29 @@ enum {COROUTINE_REGISTERS = 0xb0 / 8}; +#if defined(__SANITIZE_ADDRESS__) + #define COROUTINE_SANITIZE_ADDRESS +#elif defined(__has_feature) + #if __has_feature(address_sanitizer) + #define COROUTINE_SANITIZE_ADDRESS + #endif +#endif + +#if defined(COROUTINE_SANITIZE_ADDRESS) +#include +#include +#endif + struct coroutine_context { void **stack_pointer; void *argument; + +#if defined(COROUTINE_SANITIZE_ADDRESS) + void *fake_stack; + void *stack_base; + size_t stack_size; +#endif }; typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self); @@ -39,6 +58,12 @@ static inline void coroutine_initialize( ) { assert(start && stack && size >= 1024); +#if defined(COROUTINE_SANITIZE_ADDRESS) + context->fake_stack = NULL; + context->stack_base = stack; + context->stack_size = size; +#endif + // Stack grows down. Force 16-byte alignment. char * top = (char*)stack + size; context->stack_pointer = (void**)((uintptr_t)top & ~0xF); diff --git a/doc/hacking.md b/doc/hacking.md new file mode 100644 index 0000000000..a9fa820e7e --- /dev/null +++ b/doc/hacking.md @@ -0,0 +1,115 @@ +# Ruby Hacking Guide + +This document gives some helpful instructions which should make your +experience as a Ruby core developer easier. + +## Setup + +### Make + +It's common to want to compile things as quickly as possible. Ensuring +`make` has the right `--jobs` flag will ensure all processors are +utilized when building software projects To do this effectively, you +can set `MAKEFLAGS` in your shell configuration/profile: + +``` shell +# On macOS with Fish shell: +export MAKEFLAGS="--jobs "(sysctl -n hw.ncpu) + +# On macOS with Bash/ZSH shell: +export MAKEFLAGS="--jobs $(sysctl -n hw.ncpu)" + +# On Linux with Fish shell: +export MAKEFLAGS="--jobs "(nproc) + +# On Linux with Bash/ZSH shell: +export MAKEFLAGS="--jobs $(nproc)" +``` + +## Configure Ruby + +It's generally advisable to use a build directory. + +``` shell +./autogen.sh +mkdir build +cd build +../configure --prefix $HOME/.rubies/ruby-head +make install +``` + +### Without Documentation + +If you are frequently building Ruby, this will reduce the time it +takes to `make install`. + +``` shell +../configure --disable-install-doc +``` + +## Running Ruby + +### Run Local Test Script + +You can create a file in the Ruby source root called `test.rb`. You +can build `miniruby` and execute this script: + +``` shell +make run +``` + +If you want more of the standard library, you can use `runruby` +instead of `run`. + +## Running Tests + +You can run the following tests at once: + +``` shell +make check +``` + +### Run Bootstrap Tests + +There are a set of tests in `bootstraptest/` which cover most basic +features of the core Ruby language. + +``` shell +make test +``` + +### Run Extensive Tests + +There are extensive tests in `test/` which cover a wide range of +features of the Ruby core language. + +``` shell +make test-all +``` + +You can run specific tests by specifying their path: + +``` shell +make test-all TESTS=../test/fiber/test_io.rb +``` + +### Run Ruby Spec Suite Tests + +The [Ruby Spec Suite](https://github.com/ruby/spec/) is a test suite +that aims to provide an executable description for the behavior of the +language. + +``` shell +make test-spec +``` + +### Building with Address Sanitizer + +Using the address sanitizer is a great way to detect memory issues. + +``` shell +> ./autogen.sh +> mkdir build && cd build +> ../configure cppflags="-fsanitize=address -fno-omit-frame-pointer" optflags=-O1 LDFLAGS="-fsanitize=address -fno-omit-frame-pointer" +> +```