summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Williams <samuel.williams@oriontransfer.co.nz>2022-05-19 23:03:49 +1200
committerSamuel Williams <samuel.williams@oriontransfer.co.nz>2022-05-25 15:24:24 +1200
commit901525b1079ac02da0122a76d8e4c3546a7f80f6 (patch)
tree00cc26ecdf1933663a649adf6d688e4f557a3a5d
parentd020334e9ee6d978bbed09ce96a03a6d6d2490a6 (diff)
downloadruby-901525b1079ac02da0122a76d8e4c3546a7f80f6.tar.gz
Add support for address sanitizer for amd64 and arm64.
-rw-r--r--cont.c42
-rw-r--r--coroutine/amd64/Context.h25
-rw-r--r--coroutine/arm64/Context.h25
-rw-r--r--doc/hacking.md115
4 files changed, 205 insertions, 2 deletions
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 <sanitizer/common_interface_defs.h>
+#include <sanitizer/asan_interface.h>
+#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 <sanitizer/common_interface_defs.h>
+#include <sanitizer/asan_interface.h>
+#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"
+>
+```