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:
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
42
cont.c
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
115
doc/hacking.md
Normal 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"
|
||||||
|
>
|
||||||
|
```
|
Loading…
Reference in a new issue