1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Add support for address sanitizer for amd64 and arm64.

This commit is contained in:
Samuel Williams 2022-05-19 23:03:49 +12:00
parent d020334e9e
commit 901525b107
Notes: git 2022-05-25 12:24:51 +09:00
4 changed files with 205 additions and 2 deletions

42
cont.c
View file

@ -274,7 +274,6 @@ static ID fiber_initialize_keywords[2] = {0};
#define ERRNOMSG strerror(errno) #define ERRNOMSG strerror(errno)
// Locates the stack vacancy details for the given stack. // Locates the stack vacancy details for the given stack.
// Requires that fiber_pool_vacancy fits within one page.
inline static struct fiber_pool_vacancy * inline static struct fiber_pool_vacancy *
fiber_pool_vacancy_pointer(void * base, size_t size) 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. // Reset the current stack pointer and available size of the given stack.
inline static void inline static void
fiber_pool_stack_reset(struct fiber_pool_stack * stack) 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);
VM_ASSERT(vacancy->stack.base); 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: // Take the top item from the free list:
fiber_pool->used += 1; fiber_pool->used += 1;
@ -679,6 +700,10 @@ fiber_pool_stack_free(struct fiber_pool_stack * stack)
// Not available in all versions of Windows. // Not available in all versions of Windows.
//DiscardVirtualMemory(base, size); //DiscardVirtualMemory(base, size);
#endif #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. // 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); fiber_pool_vacancy_reset(vacancy);
// Push the vacancy into the vancancies list: // 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; pool->used -= 1;
#ifdef FIBER_POOL_ALLOCATION_FREE #ifdef FIBER_POOL_ALLOCATION_FREE
@ -751,6 +776,11 @@ static COROUTINE
fiber_entry(struct coroutine_context * from, struct coroutine_context * to) fiber_entry(struct coroutine_context * from, struct coroutine_context * to)
{ {
rb_fiber_t *fiber = to->argument; 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; rb_thread_t *thread = fiber->cont.saved_ec.thread_ptr;
#ifdef COROUTINE_PTHREAD_CONTEXT #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 (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 */ /* swap machine context */
struct coroutine_context * from = coroutine_transfer(&old_fiber->context, &new_fiber->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) { if (from == NULL) {
rb_syserr_fail(errno, "coroutine_transfer"); rb_syserr_fail(errno, "coroutine_transfer");
} }

View file

@ -19,10 +19,29 @@
enum {COROUTINE_REGISTERS = 6}; 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 struct coroutine_context
{ {
void **stack_pointer; void **stack_pointer;
void *argument; 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); 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); 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. // Stack grows down. Force 16-byte alignment.
char * top = (char*)stack + size; char * top = (char*)stack + size;
context->stack_pointer = (void**)((uintptr_t)top & ~0xF); context->stack_pointer = (void**)((uintptr_t)top & ~0xF);

View file

@ -19,10 +19,29 @@
enum {COROUTINE_REGISTERS = 0xb0 / 8}; 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 struct coroutine_context
{ {
void **stack_pointer; void **stack_pointer;
void *argument; 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); 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); 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. // Stack grows down. Force 16-byte alignment.
char * top = (char*)stack + size; char * top = (char*)stack + size;
context->stack_pointer = (void**)((uintptr_t)top & ~0xF); context->stack_pointer = (void**)((uintptr_t)top & ~0xF);

115
doc/hacking.md Normal file
View file

@ -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"
>
```